diff --git a/src/App.tsx b/src/App.tsx index ec0e963..46a4a4a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,5 @@ import { createSignal, ErrorBoundary } from "solid-js"; +import { useSelectionHandler } from "@opentui/solid"; import { Layout } from "./components/Layout"; import { Navigation } from "./components/Navigation"; import { TabNavigation } from "./components/TabNavigation"; @@ -18,6 +19,8 @@ import { useAppStore } from "./stores/app"; import { useAudio } from "./hooks/useAudio"; import { FeedVisibility } from "./types/feed"; import { useAppKeyboard } from "./hooks/useAppKeyboard"; +import { Clipboard } from "./utils/clipboard"; +import { emit } from "./utils/event-bus"; import type { TabId } from "./components/Tab"; import type { AuthScreen } from "./types/auth"; import type { Episode } from "./types/episode"; @@ -80,6 +83,21 @@ export function App() { }, }); + // Copy selected text to clipboard when selection ends (mouse release) + useSelectionHandler((selection: any) => { + if (!selection) return + const text = selection.getSelectedText?.() + if (!text || text.trim().length === 0) return + + Clipboard.copy(text).then(() => { + emit("toast.show", { + message: "Copied to clipboard", + variant: "info", + duration: 1500, + }) + }).catch(() => {}) + }) + const getPanels = () => { const tab = activeTab(); diff --git a/src/api/rss-parser.ts b/src/api/rss-parser.ts index c451d05..50acd5c 100644 --- a/src/api/rss-parser.ts +++ b/src/api/rss-parser.ts @@ -1,5 +1,7 @@ import type { Podcast } from "../types/podcast" import type { Episode, EpisodeType } from "../types/episode" +import { detectContentType, ContentType } from "../utils/rss-content-detector" +import { htmlToText } from "../utils/html-to-text" const getTagValue = (xml: string, tag: string): string => { const match = xml.match(new RegExp(`<${tag}[^>]*>([\\s\\S]*?)`, "i")) @@ -22,6 +24,20 @@ const decodeEntities = (value: string) => .replace(/"/g, '"') .replace(/'/g, "'") +/** + * Clean a description field: detect HTML vs plain text, and convert + * HTML to readable plain text. Plain text just gets entity decoding. + */ +const cleanDescription = (raw: string): string => { + if (!raw) return "" + const decoded = decodeEntities(raw) + const type = detectContentType(decoded) + if (type === ContentType.HTML) { + return htmlToText(decoded) + } + return decoded +} + /** * Parse an itunes:duration value which can be: * - "HH:MM:SS" @@ -61,14 +77,14 @@ const parseEpisodeType = (raw: string): EpisodeType | undefined => { export const parseRSSFeed = (xml: string, feedUrl: string): Podcast & { episodes: Episode[] } => { const channel = xml.match(//i)?.[0] ?? xml const title = decodeEntities(getTagValue(channel, "title")) || "Untitled Podcast" - const description = decodeEntities(getTagValue(channel, "description")) + const description = cleanDescription(getTagValue(channel, "description")) const author = decodeEntities(getTagValue(channel, "itunes:author")) const lastUpdated = new Date() const items = channel.match(//gi) ?? [] const episodes = items.map((item, index) => { const epTitle = decodeEntities(getTagValue(item, "title")) || `Episode ${index + 1}` - const epDescription = decodeEntities(getTagValue(item, "description")) + const epDescription = cleanDescription(getTagValue(item, "description")) const pubDate = new Date(getTagValue(item, "pubDate") || Date.now()) // Audio URL + file size + MIME type from diff --git a/src/stores/app.ts b/src/stores/app.ts index 923d404..c99b485 100644 --- a/src/stores/app.ts +++ b/src/stores/app.ts @@ -3,8 +3,11 @@ import { DEFAULT_THEME, THEME_JSON } from "../constants/themes" import type { AppSettings, AppState, ThemeColors, ThemeName, ThemeMode, UserPreferences } from "../types/settings" import { resolveTheme } from "../utils/theme-resolver" import type { ThemeJson } from "../types/theme-schema" - -const STORAGE_KEY = "podtui_app_state" +import { + loadAppStateFromFile, + saveAppStateToFile, + migrateAppStateFromLocalStorage, +} from "../utils/app-persistence" const defaultSettings: AppSettings = { theme: "system", @@ -24,33 +27,21 @@ const defaultState: AppState = { customTheme: DEFAULT_THEME, } -const loadState = (): AppState => { - if (typeof localStorage === "undefined") return defaultState - try { - const raw = localStorage.getItem(STORAGE_KEY) - if (!raw) return defaultState - const parsed = JSON.parse(raw) as Partial - return { - settings: { ...defaultSettings, ...parsed.settings }, - preferences: { ...defaultPreferences, ...parsed.preferences }, - customTheme: { ...DEFAULT_THEME, ...parsed.customTheme }, - } - } catch { - return defaultState - } -} - -const saveState = (state: AppState) => { - if (typeof localStorage === "undefined") return - try { - localStorage.setItem(STORAGE_KEY, JSON.stringify(state)) - } catch { - // ignore storage errors - } -} - export function createAppStore() { - const [state, setState] = createSignal(loadState()) + // Start with defaults; async load will update once ready + const [state, setState] = createSignal(defaultState) + + // Fire-and-forget async initialisation + const init = async () => { + await migrateAppStateFromLocalStorage() + const loaded = await loadAppStateFromFile() + setState(loaded) + } + init() + + const saveState = (next: AppState) => { + saveAppStateToFile(next).catch(() => {}) + } const updateState = (next: AppState) => { setState(next) diff --git a/src/stores/feed.ts b/src/stores/feed.ts index 0221384..cf6f842 100644 --- a/src/stores/feed.ts +++ b/src/stores/feed.ts @@ -11,6 +11,14 @@ import type { Episode, EpisodeStatus } from "../types/episode" import type { PodcastSource, SourceType } from "../types/source" import { DEFAULT_SOURCES } from "../types/source" import { parseRSSFeed } from "../api/rss-parser" +import { + loadFeedsFromFile, + saveFeedsToFile, + loadSourcesFromFile, + saveSourcesToFile, + migrateFeedsFromLocalStorage, + migrateSourcesFromLocalStorage, +} from "../utils/feeds-persistence" /** Max episodes to fetch on refresh */ const MAX_EPISODES_REFRESH = 50 @@ -18,85 +26,30 @@ const MAX_EPISODES_REFRESH = 50 /** Max episodes to fetch on initial subscribe */ const MAX_EPISODES_SUBSCRIBE = 20 -/** Storage keys */ -const STORAGE_KEYS = { - feeds: "podtui_feeds", - sources: "podtui_sources", -} - -/** Load feeds from localStorage */ -function loadFeeds(): Feed[] { - if (typeof localStorage === "undefined") { - return [] - } - - try { - const stored = localStorage.getItem(STORAGE_KEYS.feeds) - if (stored) { - const parsed = JSON.parse(stored) - // Convert date strings - return parsed.map((feed: Feed) => ({ - ...feed, - lastUpdated: new Date(feed.lastUpdated), - podcast: { - ...feed.podcast, - lastUpdated: new Date(feed.podcast.lastUpdated), - }, - episodes: feed.episodes.map((ep: Episode) => ({ - ...ep, - pubDate: new Date(ep.pubDate), - })), - })) - } - } catch { - // Ignore errors - } - - return [] -} - -/** Save feeds to localStorage */ +/** Save feeds to file (async, fire-and-forget) */ function saveFeeds(feeds: Feed[]): void { - if (typeof localStorage === "undefined") return - try { - localStorage.setItem(STORAGE_KEYS.feeds, JSON.stringify(feeds)) - } catch { - // Ignore errors - } + saveFeedsToFile(feeds).catch(() => {}) } -/** Load sources from localStorage */ -function loadSources(): PodcastSource[] { - if (typeof localStorage === "undefined") { - return [...DEFAULT_SOURCES] - } - - try { - const stored = localStorage.getItem(STORAGE_KEYS.sources) - if (stored) { - return JSON.parse(stored) - } - } catch { - // Ignore errors - } - - return [...DEFAULT_SOURCES] -} - -/** Save sources to localStorage */ +/** Save sources to file (async, fire-and-forget) */ function saveSources(sources: PodcastSource[]): void { - if (typeof localStorage === "undefined") return - try { - localStorage.setItem(STORAGE_KEYS.sources, JSON.stringify(sources)) - } catch { - // Ignore errors - } + saveSourcesToFile(sources).catch(() => {}) } /** Create feed store */ export function createFeedStore() { - const [feeds, setFeeds] = createSignal(loadFeeds()) - const [sources, setSources] = createSignal(loadSources()) + const [feeds, setFeeds] = createSignal([]) + const [sources, setSources] = createSignal([...DEFAULT_SOURCES]) + + // Async initialization: migrate from localStorage, then load from file + ;(async () => { + await migrateFeedsFromLocalStorage() + await migrateSourcesFromLocalStorage() + const loadedFeeds = await loadFeedsFromFile() + if (loadedFeeds.length > 0) setFeeds(loadedFeeds) + const loadedSources = await loadSourcesFromFile() + if (loadedSources && loadedSources.length > 0) setSources(loadedSources) + })() const [filter, setFilter] = createSignal({ visibility: "all", sortBy: "updated" as FeedSortField, diff --git a/src/stores/progress.ts b/src/stores/progress.ts index 22080d3..fa6bbeb 100644 --- a/src/stores/progress.ts +++ b/src/stores/progress.ts @@ -1,14 +1,17 @@ /** * Episode progress store for PodTUI * - * Persists per-episode playback progress to localStorage. + * Persists per-episode playback progress to a JSON file in XDG_CONFIG_HOME. * Tracks position, duration, completion, and last-played timestamp. */ import { createSignal } from "solid-js" import type { Progress } from "../types/episode" - -const STORAGE_KEY = "podtui_progress" +import { + loadProgressFromFile, + saveProgressToFile, + migrateProgressFromLocalStorage, +} from "../utils/app-persistence" /** Threshold (fraction 0-1) at which an episode is considered completed */ const COMPLETION_THRESHOLD = 0.95 @@ -16,48 +19,42 @@ const COMPLETION_THRESHOLD = 0.95 /** Minimum seconds of progress before persisting */ const MIN_POSITION_TO_SAVE = 5 -// --- localStorage helpers --- - -function loadProgress(): Record { - try { - const raw = localStorage.getItem(STORAGE_KEY) - if (!raw) return {} - const parsed = JSON.parse(raw) as Record - const result: Record = {} - for (const [key, value] of Object.entries(parsed)) { - const p = value as Record - result[key] = { - episodeId: p.episodeId as string, - position: p.position as number, - duration: p.duration as number, - timestamp: new Date(p.timestamp as string), - playbackSpeed: p.playbackSpeed as number | undefined, - } - } - return result - } catch { - return {} - } -} - -function saveProgress(data: Record): void { - try { - localStorage.setItem(STORAGE_KEY, JSON.stringify(data)) - } catch { - // Quota exceeded or unavailable — silently ignore - } -} - // --- Singleton store --- -const [progressMap, setProgressMap] = createSignal>( - loadProgress(), -) +const [progressMap, setProgressMap] = createSignal>({}) +/** Persist current progress map to file (fire-and-forget) */ function persist(): void { - saveProgress(progressMap()) + saveProgressToFile(progressMap()).catch(() => {}) } +/** Parse raw progress entries from file, reviving Date objects */ +function parseProgressEntries(raw: Record): Record { + const result: Record = {} + for (const [key, value] of Object.entries(raw)) { + const p = value as Record + result[key] = { + episodeId: p.episodeId as string, + position: p.position as number, + duration: p.duration as number, + timestamp: new Date(p.timestamp as string), + playbackSpeed: p.playbackSpeed as number | undefined, + } + } + return result +} + +/** Async initialisation — migrate from localStorage then load from file */ +async function initProgress(): Promise { + await migrateProgressFromLocalStorage() + const raw = await loadProgressFromFile() + const parsed = parseProgressEntries(raw as Record) + setProgressMap(parsed) +} + +// Fire-and-forget init +initProgress() + function createProgressStore() { return { /** diff --git a/src/utils/app-persistence.ts b/src/utils/app-persistence.ts new file mode 100644 index 0000000..56819f0 --- /dev/null +++ b/src/utils/app-persistence.ts @@ -0,0 +1,163 @@ +/** + * App state persistence via JSON file in XDG_CONFIG_HOME + * + * Reads and writes app settings, preferences, and custom theme to a JSON file + * instead of localStorage. Provides migration from localStorage on first run. + */ + +import { ensureConfigDir, getConfigFilePath } from "./config-dir" +import { backupConfigFile } from "./config-backup" +import type { AppState, AppSettings, UserPreferences, ThemeColors } from "../types/settings" +import { DEFAULT_THEME } from "../constants/themes" + +const APP_STATE_FILE = "app-state.json" +const PROGRESS_FILE = "progress.json" + +const LEGACY_APP_STATE_KEY = "podtui_app_state" +const LEGACY_PROGRESS_KEY = "podtui_progress" + +// --- Defaults --- + +const defaultSettings: AppSettings = { + theme: "system", + fontSize: 14, + playbackSpeed: 1, + downloadPath: "", +} + +const defaultPreferences: UserPreferences = { + showExplicit: false, + autoDownload: false, +} + +const defaultState: AppState = { + settings: defaultSettings, + preferences: defaultPreferences, + customTheme: DEFAULT_THEME, +} + +// --- App State --- + +/** Load app state from JSON file */ +export async function loadAppStateFromFile(): Promise { + try { + const filePath = getConfigFilePath(APP_STATE_FILE) + const file = Bun.file(filePath) + if (!(await file.exists())) return defaultState + + const raw = await file.json() + if (!raw || typeof raw !== "object") return defaultState + + const parsed = raw as Partial + return { + settings: { ...defaultSettings, ...parsed.settings }, + preferences: { ...defaultPreferences, ...parsed.preferences }, + customTheme: { ...DEFAULT_THEME, ...parsed.customTheme }, + } + } catch { + return defaultState + } +} + +/** Save app state to JSON file */ +export async function saveAppStateToFile(state: AppState): Promise { + try { + await ensureConfigDir() + await backupConfigFile(APP_STATE_FILE) + const filePath = getConfigFilePath(APP_STATE_FILE) + await Bun.write(filePath, JSON.stringify(state, null, 2)) + } catch { + // Silently ignore write errors + } +} + +/** + * Migrate app state from localStorage to file. + * Only runs once — if the state file already exists, it's a no-op. + */ +export async function migrateAppStateFromLocalStorage(): Promise { + try { + const filePath = getConfigFilePath(APP_STATE_FILE) + const file = Bun.file(filePath) + if (await file.exists()) return false + + if (typeof localStorage === "undefined") return false + + const raw = localStorage.getItem(LEGACY_APP_STATE_KEY) + if (!raw) return false + + const parsed = JSON.parse(raw) as Partial + const state: AppState = { + settings: { ...defaultSettings, ...parsed.settings }, + preferences: { ...defaultPreferences, ...parsed.preferences }, + customTheme: { ...DEFAULT_THEME, ...parsed.customTheme }, + } + + await saveAppStateToFile(state) + return true + } catch { + return false + } +} + +// --- Progress --- + +interface ProgressEntry { + episodeId: string + position: number + duration: number + timestamp: string | Date + playbackSpeed?: number +} + +/** Load progress map from JSON file */ +export async function loadProgressFromFile(): Promise> { + try { + const filePath = getConfigFilePath(PROGRESS_FILE) + const file = Bun.file(filePath) + if (!(await file.exists())) return {} + + const raw = await file.json() + if (!raw || typeof raw !== "object") return {} + return raw as Record + } catch { + return {} + } +} + +/** Save progress map to JSON file */ +export async function saveProgressToFile(data: Record): Promise { + try { + await ensureConfigDir() + await backupConfigFile(PROGRESS_FILE) + const filePath = getConfigFilePath(PROGRESS_FILE) + await Bun.write(filePath, JSON.stringify(data, null, 2)) + } catch { + // Silently ignore write errors + } +} + +/** + * Migrate progress from localStorage to file. + * Only runs once — if the progress file already exists, it's a no-op. + */ +export async function migrateProgressFromLocalStorage(): Promise { + try { + const filePath = getConfigFilePath(PROGRESS_FILE) + const file = Bun.file(filePath) + if (await file.exists()) return false + + if (typeof localStorage === "undefined") return false + + const raw = localStorage.getItem(LEGACY_PROGRESS_KEY) + if (!raw) return false + + const parsed = JSON.parse(raw) + if (!parsed || typeof parsed !== "object") return false + + await saveProgressToFile(parsed as Record) + return true + } catch { + return false + } +} diff --git a/src/utils/config-backup.ts b/src/utils/config-backup.ts new file mode 100644 index 0000000..6f29e04 --- /dev/null +++ b/src/utils/config-backup.ts @@ -0,0 +1,96 @@ +/** + * Config file backup utility for PodTUI + * + * Creates timestamped backups of config files before updates. + * Keeps the most recent N backups and cleans up older ones. + */ + +import { readdir, unlink } from "fs/promises" +import path from "path" +import { getConfigDir, ensureConfigDir } from "./config-dir" + +/** Maximum number of backup files to keep per config file */ +const MAX_BACKUPS = 5 + +/** + * Generate a timestamped backup filename. + * Example: feeds.json -> feeds.json.2026-02-05T120000.backup + */ +function backupFilename(originalName: string): string { + const ts = new Date().toISOString().replace(/[:.]/g, "").slice(0, 15) + return `${originalName}.${ts}.backup` +} + +/** + * Create a backup of a config file before overwriting it. + * No-op if the source file does not exist. + */ +export async function backupConfigFile(filename: string): Promise { + try { + await ensureConfigDir() + const dir = getConfigDir() + const srcPath = path.join(dir, filename) + const srcFile = Bun.file(srcPath) + + if (!(await srcFile.exists())) return false + + const content = await srcFile.text() + if (!content || content.trim().length === 0) return false + + const backupName = backupFilename(filename) + const backupPath = path.join(dir, backupName) + await Bun.write(backupPath, content) + + // Clean up old backups + await pruneBackups(filename) + + return true + } catch { + return false + } +} + +/** + * Keep only the most recent MAX_BACKUPS backup files for a given config file. + */ +async function pruneBackups(filename: string): Promise { + try { + const dir = getConfigDir() + const entries = await readdir(dir) + + // Match pattern: filename.*.backup + const prefix = `${filename}.` + const suffix = ".backup" + const backups = entries + .filter((e) => e.startsWith(prefix) && e.endsWith(suffix)) + .sort() // Lexicographic sort works because timestamps are ISO-like + + if (backups.length <= MAX_BACKUPS) return + + const toRemove = backups.slice(0, backups.length - MAX_BACKUPS) + for (const name of toRemove) { + await unlink(path.join(dir, name)).catch(() => {}) + } + } catch { + // Silently ignore cleanup errors + } +} + +/** + * List existing backup files for a given config file, newest first. + */ +export async function listBackups(filename: string): Promise { + try { + const dir = getConfigDir() + const entries = await readdir(dir) + + const prefix = `${filename}.` + const suffix = ".backup" + return entries + .filter((e) => e.startsWith(prefix) && e.endsWith(suffix)) + .sort() + .reverse() + } catch { + return [] + } +} diff --git a/src/utils/config-dir.ts b/src/utils/config-dir.ts new file mode 100644 index 0000000..cc22cf7 --- /dev/null +++ b/src/utils/config-dir.ts @@ -0,0 +1,44 @@ +/** + * XDG_CONFIG_HOME directory setup for PodTUI + * + * Handles config directory detection and creation following the XDG Base + * Directory Specification. Falls back to ~/.config when XDG_CONFIG_HOME + * is not set. + */ + +import { mkdir } from "fs/promises" +import path from "path" + +/** Application config directory name */ +const APP_DIR_NAME = "podtui" + +/** Resolve the XDG_CONFIG_HOME directory, defaulting to ~/.config */ +export function getXdgConfigHome(): string { + const xdg = process.env.XDG_CONFIG_HOME + if (xdg) return xdg + + const home = process.env.HOME ?? process.env.USERPROFILE ?? "" + if (!home) throw new Error("Cannot determine home directory") + + return path.join(home, ".config") +} + +/** Get the application-specific config directory path */ +export function getConfigDir(): string { + return path.join(getXdgConfigHome(), APP_DIR_NAME) +} + +/** Get the path for a specific config file */ +export function getConfigFilePath(filename: string): string { + return path.join(getConfigDir(), filename) +} + +/** + * Ensure the application config directory exists. + * Creates it recursively if needed. + */ +export async function ensureConfigDir(): Promise { + const dir = getConfigDir() + await mkdir(dir, { recursive: true }) + return dir +} diff --git a/src/utils/config-validation.ts b/src/utils/config-validation.ts new file mode 100644 index 0000000..ddbeb88 --- /dev/null +++ b/src/utils/config-validation.ts @@ -0,0 +1,166 @@ +/** + * Config file validation and migration for PodTUI + * + * Validates JSON structure of config files, handles corrupted files + * gracefully (falling back to defaults), and provides a single + * entry-point to migrate all localStorage data to XDG config files. + */ + +import { getConfigFilePath } from "./config-dir" +import { + migrateAppStateFromLocalStorage, + migrateProgressFromLocalStorage, +} from "./app-persistence" +import { + migrateFeedsFromLocalStorage, + migrateSourcesFromLocalStorage, +} from "./feeds-persistence" + +// --- Validation helpers --- + +/** Check that a value is a non-null object */ +function isObject(v: unknown): v is Record { + return v !== null && typeof v === "object" && !Array.isArray(v) +} + +/** Validate AppState JSON structure */ +export function validateAppState(data: unknown): { valid: boolean; errors: string[] } { + const errors: string[] = [] + if (!isObject(data)) { + return { valid: false, errors: ["app-state.json is not an object"] } + } + + // settings + if (data.settings !== undefined) { + if (!isObject(data.settings)) { + errors.push("settings must be an object") + } else { + const s = data.settings as Record + if (s.theme !== undefined && typeof s.theme !== "string") errors.push("settings.theme must be a string") + if (s.fontSize !== undefined && typeof s.fontSize !== "number") errors.push("settings.fontSize must be a number") + if (s.playbackSpeed !== undefined && typeof s.playbackSpeed !== "number") errors.push("settings.playbackSpeed must be a number") + if (s.downloadPath !== undefined && typeof s.downloadPath !== "string") errors.push("settings.downloadPath must be a string") + } + } + + // preferences + if (data.preferences !== undefined) { + if (!isObject(data.preferences)) { + errors.push("preferences must be an object") + } else { + const p = data.preferences as Record + if (p.showExplicit !== undefined && typeof p.showExplicit !== "boolean") errors.push("preferences.showExplicit must be a boolean") + if (p.autoDownload !== undefined && typeof p.autoDownload !== "boolean") errors.push("preferences.autoDownload must be a boolean") + } + } + + // customTheme + if (data.customTheme !== undefined && !isObject(data.customTheme)) { + errors.push("customTheme must be an object") + } + + return { valid: errors.length === 0, errors } +} + +/** Validate feeds JSON structure */ +export function validateFeeds(data: unknown): { valid: boolean; errors: string[] } { + const errors: string[] = [] + if (!Array.isArray(data)) { + return { valid: false, errors: ["feeds.json is not an array"] } + } + + for (let i = 0; i < data.length; i++) { + const feed = data[i] + if (!isObject(feed)) { + errors.push(`feeds[${i}] is not an object`) + continue + } + if (typeof feed.id !== "string") errors.push(`feeds[${i}].id must be a string`) + if (!isObject(feed.podcast)) errors.push(`feeds[${i}].podcast must be an object`) + if (!Array.isArray(feed.episodes)) errors.push(`feeds[${i}].episodes must be an array`) + } + + return { valid: errors.length === 0, errors } +} + +/** Validate progress JSON structure */ +export function validateProgress(data: unknown): { valid: boolean; errors: string[] } { + const errors: string[] = [] + if (!isObject(data)) { + return { valid: false, errors: ["progress.json is not an object"] } + } + + for (const [key, value] of Object.entries(data)) { + if (!isObject(value)) { + errors.push(`progress["${key}"] is not an object`) + continue + } + const p = value as Record + if (typeof p.episodeId !== "string") errors.push(`progress["${key}"].episodeId must be a string`) + if (typeof p.position !== "number") errors.push(`progress["${key}"].position must be a number`) + if (typeof p.duration !== "number") errors.push(`progress["${key}"].duration must be a number`) + } + + return { valid: errors.length === 0, errors } +} + +// --- Safe config file reading --- + +/** + * Safely read and validate a config file. + * Returns the parsed data if valid, or null if the file is missing/corrupt. + */ +export async function safeReadConfigFile( + filename: string, + validator: (data: unknown) => { valid: boolean; errors: string[] }, +): Promise<{ data: T | null; errors: string[] }> { + try { + const filePath = getConfigFilePath(filename) + const file = Bun.file(filePath) + if (!(await file.exists())) { + return { data: null, errors: [] } + } + + const text = await file.text() + let parsed: unknown + try { + parsed = JSON.parse(text) + } catch { + return { data: null, errors: [`${filename}: invalid JSON`] } + } + + const result = validator(parsed) + if (!result.valid) { + return { data: null, errors: result.errors } + } + + return { data: parsed as T, errors: [] } + } catch (err) { + return { data: null, errors: [`${filename}: ${String(err)}`] } + } +} + +// --- Unified migration --- + +/** + * Run all localStorage -> file migrations. + * Safe to call multiple times; each migration is a no-op if the target + * file already exists. + * + * Returns a summary of what was migrated. + */ +export async function migrateAllFromLocalStorage(): Promise<{ + appState: boolean + progress: boolean + feeds: boolean + sources: boolean +}> { + const [appState, progress, feeds, sources] = await Promise.all([ + migrateAppStateFromLocalStorage(), + migrateProgressFromLocalStorage(), + migrateFeedsFromLocalStorage(), + migrateSourcesFromLocalStorage(), + ]) + + return { appState, progress, feeds, sources } +} diff --git a/src/utils/event-bus.ts b/src/utils/event-bus.ts index e52a230..2cd8d72 100644 --- a/src/utils/event-bus.ts +++ b/src/utils/event-bus.ts @@ -107,6 +107,9 @@ export type AppEvents = { "dialog.open": { dialogId: string } "dialog.close": { dialogId?: string } "command.execute": { command: string; args?: unknown } + "clipboard.copied": { text: string } + "selection.start": { x: number; y: number } + "selection.end": { text: string } } // Type-safe emit and on functions diff --git a/src/utils/feeds-persistence.ts b/src/utils/feeds-persistence.ts new file mode 100644 index 0000000..2f3a54c --- /dev/null +++ b/src/utils/feeds-persistence.ts @@ -0,0 +1,132 @@ +/** + * Feeds persistence via JSON file in XDG_CONFIG_HOME + * + * Reads and writes feeds to a JSON file instead of localStorage. + * Provides migration from localStorage on first run. + */ + +import { ensureConfigDir, getConfigFilePath } from "./config-dir" +import { backupConfigFile } from "./config-backup" +import type { Feed } from "../types/feed" + +const FEEDS_FILE = "feeds.json" +const SOURCES_FILE = "sources.json" + +/** Deserialize date strings back to Date objects in feed data */ +function reviveDates(feed: Feed): Feed { + return { + ...feed, + lastUpdated: new Date(feed.lastUpdated), + podcast: { + ...feed.podcast, + lastUpdated: new Date(feed.podcast.lastUpdated), + }, + episodes: feed.episodes.map((ep) => ({ + ...ep, + pubDate: new Date(ep.pubDate), + })), + } +} + +/** Load feeds from JSON file */ +export async function loadFeedsFromFile(): Promise { + try { + const filePath = getConfigFilePath(FEEDS_FILE) + const file = Bun.file(filePath) + if (!(await file.exists())) return [] + + const raw = await file.json() + if (!Array.isArray(raw)) return [] + return raw.map(reviveDates) + } catch { + return [] + } +} + +/** Save feeds to JSON file */ +export async function saveFeedsToFile(feeds: Feed[]): Promise { + try { + await ensureConfigDir() + await backupConfigFile(FEEDS_FILE) + const filePath = getConfigFilePath(FEEDS_FILE) + await Bun.write(filePath, JSON.stringify(feeds, null, 2)) + } catch { + // Silently ignore write errors + } +} + +/** Load sources from JSON file */ +export async function loadSourcesFromFile(): Promise { + try { + const filePath = getConfigFilePath(SOURCES_FILE) + const file = Bun.file(filePath) + if (!(await file.exists())) return null + + const raw = await file.json() + if (!Array.isArray(raw)) return null + return raw as T[] + } catch { + return null + } +} + +/** Save sources to JSON file */ +export async function saveSourcesToFile(sources: T[]): Promise { + try { + await ensureConfigDir() + await backupConfigFile(SOURCES_FILE) + const filePath = getConfigFilePath(SOURCES_FILE) + await Bun.write(filePath, JSON.stringify(sources, null, 2)) + } catch { + // Silently ignore write errors + } +} + +/** + * Migrate feeds from localStorage to file. + * Only runs once — if the feeds file already exists, it's a no-op. + */ +export async function migrateFeedsFromLocalStorage(): Promise { + try { + const filePath = getConfigFilePath(FEEDS_FILE) + const file = Bun.file(filePath) + if (await file.exists()) return false // Already migrated + + if (typeof localStorage === "undefined") return false + + const raw = localStorage.getItem("podtui_feeds") + if (!raw) return false + + const feeds = JSON.parse(raw) as Feed[] + if (!Array.isArray(feeds) || feeds.length === 0) return false + + await saveFeedsToFile(feeds) + return true + } catch { + return false + } +} + +/** + * Migrate sources from localStorage to file. + */ +export async function migrateSourcesFromLocalStorage(): Promise { + try { + const filePath = getConfigFilePath(SOURCES_FILE) + const file = Bun.file(filePath) + if (await file.exists()) return false + + if (typeof localStorage === "undefined") return false + + const raw = localStorage.getItem("podtui_sources") + if (!raw) return false + + const sources = JSON.parse(raw) + if (!Array.isArray(sources) || sources.length === 0) return false + + await saveSourcesToFile(sources) + return true + } catch { + return false + } +} diff --git a/src/utils/html-to-text.ts b/src/utils/html-to-text.ts new file mode 100644 index 0000000..68b7ae2 --- /dev/null +++ b/src/utils/html-to-text.ts @@ -0,0 +1,111 @@ +/** + * HTML-to-text conversion for PodTUI + * + * Converts HTML content from RSS feed descriptions into clean plain text + * suitable for display in the terminal. Preserves paragraph structure, + * converts lists to bulleted text, and strips all tags. + */ + +/** + * Convert HTML content to readable plain text. + * + * - Block elements (

,

,
, headings,
  • ) become line breaks + * -
  • items get a bullet prefix + * - text becomes "text (url)" + * - All other tags are stripped + * - HTML entities are decoded + * - Excessive whitespace is collapsed + */ +export function htmlToText(html: string): string { + if (!html) return "" + + let text = html + + // Strip CDATA wrappers + text = text.replace(//gi, "$1") + + // Replace
    /
    with newline + text = text.replace(//gi, "\n") + + // Replace
    with a separator line + text = text.replace(//gi, "\n---\n") + + // Block-level elements get newlines before/after + text = text.replace(/<\/?(p|div|blockquote|pre|h[1-6]|table|tr|section|article|header|footer)[\s>][^>]*>/gi, "\n") + + // List items get bullet prefix + text = text.replace(/]*>/gi, "\n - ") + text = text.replace(/<\/li>/gi, "") + + // Strip list wrappers + text = text.replace(/<\/?(ul|ol|dl|dt|dd)[^>]*>/gi, "\n") + + // Convert links: text -> text (url) + text = text.replace(/]*href=["']([^"']*)["'][^>]*>([\s\S]*?)<\/a>/gi, (_, href, linkText) => { + const cleanText = stripTags(linkText).trim() + if (!cleanText) return href + // Don't duplicate if the link text IS the URL + if (cleanText === href || cleanText === href.replace(/^https?:\/\//, "")) return cleanText + return `${cleanText} (${href})` + }) + + // Strip all remaining tags + text = stripTags(text) + + // Decode HTML entities + text = decodeHtmlEntities(text) + + // Collapse multiple blank lines into at most two newlines + text = text.replace(/\n{3,}/g, "\n\n") + + // Collapse runs of spaces/tabs (but not newlines) on each line + text = text + .split("\n") + .map((line) => line.replace(/[ \t]+/g, " ").trim()) + .join("\n") + + return text.trim() +} + +/** Strip all HTML/XML tags from a string */ +function stripTags(html: string): string { + return html.replace(/<[^>]*>/g, "") +} + +/** Decode common HTML entities */ +function decodeHtmlEntities(text: string): string { + return text + // Named entities + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/&/g, "&") + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/'/g, "'") + .replace(/ /g, " ") + .replace(/—/g, "\u2014") + .replace(/–/g, "\u2013") + .replace(/…/g, "\u2026") + .replace(/«/g, "\u00AB") + .replace(/»/g, "\u00BB") + .replace(/“/g, "\u201C") + .replace(/”/g, "\u201D") + .replace(/‘/g, "\u2018") + .replace(/’/g, "\u2019") + .replace(/•/g, "\u2022") + .replace(/©/g, "\u00A9") + .replace(/®/g, "\u00AE") + .replace(/™/g, "\u2122") + .replace(/°/g, "\u00B0") + .replace(/×/g, "\u00D7") + // Numeric entities (decimal) + .replace(/&#(\d+);/g, (_, code) => { + const n = parseInt(code, 10) + return n > 0 && n < 0x10ffff ? String.fromCodePoint(n) : "" + }) + // Numeric entities (hex) + .replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => { + const n = parseInt(hex, 16) + return n > 0 && n < 0x10ffff ? String.fromCodePoint(n) : "" + }) +} diff --git a/src/utils/rss-content-detector.ts b/src/utils/rss-content-detector.ts new file mode 100644 index 0000000..303f7a4 --- /dev/null +++ b/src/utils/rss-content-detector.ts @@ -0,0 +1,40 @@ +/** + * RSS content type detection for PodTUI + * + * Determines whether RSS feed content (description, etc.) is HTML or plain + * text so the appropriate parsing path can be selected. + */ + +export enum ContentType { + HTML = "html", + PLAIN_TEXT = "plain_text", + UNKNOWN = "unknown", +} + +/** Common HTML tags found in RSS descriptions */ +const HTML_TAG_RE = /<\s*\/?\s*(div|p|br|a|b|i|em|strong|ul|ol|li|span|h[1-6]|img|table|tr|td|blockquote|pre|code|hr)\b[^>]*\/?>/i + +/** HTML entity patterns beyond the basic five (& etc.) */ +const HTML_ENTITY_RE = /&(nbsp|mdash|ndash|hellip|laquo|raquo|ldquo|rdquo|lsquo|rsquo|bull|#\d{2,5}|#x[0-9a-fA-F]{2,4});/ + +/** CDATA wrapper — content inside is almost always HTML */ +const CDATA_RE = /^\s* 24 +- 23 -> 25 +- 24 -> 26 +- 25 -> 26 +- 26 -> 27 + +Exit criteria +- Feeds are persisted to XDG_CONFIG_HOME/podcast-tui-app/feeds.json +- Themes are persisted to XDG_CONFIG_HOME/podcast-tui-app/themes.json +- Config file validation ensures data integrity +- Migration from localStorage works seamlessly diff --git a/tasks/discover-categories-fix/20-category-filter-debug.md b/tasks/discover-categories-fix/20-category-filter-debug.md new file mode 100644 index 0000000..50a4b8a --- /dev/null +++ b/tasks/discover-categories-fix/20-category-filter-debug.md @@ -0,0 +1,47 @@ +# 20. Debug Category Filter Implementation + +meta: + id: discover-categories-fix-20 + feature: discover-categories-fix + priority: P2 + depends_on: [] + tags: [debugging, discover, categories] + +objective: +- Identify why category filter is not working +- Analyze CategoryFilter component behavior +- Trace state flow from category selection to show filtering + +deliverables: +- Debugged category filter logic +- Identified root cause of issue +- Test cases to verify fix + +steps: +1. Review CategoryFilter component implementation +2. Review DiscoverPage category selection handler +3. Review discover store category filtering logic +4. Add console logging to trace state changes +5. Test with various category selections + +tests: +- Debug: Test category selection in UI +- Debug: Verify state updates in console +- Manual: Select different categories and observe behavior + +acceptance_criteria: +- Root cause of category filter issue identified +- State flow from category to shows is traced +- Specific code causing issue identified + +validation: +- Run app and select categories +- Check console for state updates +- Verify which component is not responding correctly + +notes: +- Check if categoryIndex signal is updated +- Verify discoverStore.setSelectedCategory() is called +- Check if filteredPodcasts() is recalculated +- Look for race conditions or state sync issues +- Add temporary logging to trace state changes diff --git a/tasks/discover-categories-fix/21-category-state-sync.md b/tasks/discover-categories-fix/21-category-state-sync.md new file mode 100644 index 0000000..e2470fc --- /dev/null +++ b/tasks/discover-categories-fix/21-category-state-sync.md @@ -0,0 +1,47 @@ +# 21. Fix Category State Synchronization + +meta: + id: discover-categories-fix-21 + feature: discover-categories-fix + priority: P2 + depends_on: [discover-categories-fix-20] + tags: [state-management, discover, categories] + +objective: +- Ensure category state is properly synchronized across components +- Fix state updates not triggering re-renders +- Ensure category selection persists correctly + +deliverables: +- Fixed state synchronization logic +- Updated category selection handlers +- Verified state propagation + +steps: +1. Fix category state update handlers in DiscoverPage +2. Ensure discoverStore.setSelectedCategory() is called correctly +3. Fix signal updates to trigger component re-renders +4. Test state synchronization across component updates +5. Verify category state persists on navigation + +tests: +- Unit: Test state update handlers +- Integration: Test category selection and state updates +- Manual: Navigate between tabs and verify category state + +acceptance_criteria: +- Category state updates propagate correctly +- Component re-renders when category changes +- Category selection persists across navigation + +validation: +- Select category and verify show list updates +- Switch tabs and back, verify category still selected +- Test category navigation with keyboard + +notes: +- Check if signals are properly created and updated +- Verify discoverStore state is reactive +- Ensure CategoryFilter and TrendingShows receive updated data +- Test with multiple category selections +- Add state persistence if needed diff --git a/tasks/discover-categories-fix/22-category-navigation-fix.md b/tasks/discover-categories-fix/22-category-navigation-fix.md new file mode 100644 index 0000000..0887d11 --- /dev/null +++ b/tasks/discover-categories-fix/22-category-navigation-fix.md @@ -0,0 +1,47 @@ +# 22. Fix Category Keyboard Navigation + +meta: + id: discover-categories-fix-22 + feature: discover-categories-fix + priority: P2 + depends_on: [discover-categories-fix-21] + tags: [keyboard, navigation, discover] + +objective: +- Fix keyboard navigation for categories +- Ensure category selection works with arrow keys +- Fix category index tracking during navigation + +deliverables: +- Fixed keyboard navigation handlers +- Updated category index tracking +- Verified navigation works correctly + +steps: +1. Review keyboard navigation in DiscoverPage +2. Fix category index signal updates +3. Ensure categoryIndex signal is updated on arrow key presses +4. Test category navigation with arrow keys +5. Fix category selection on Enter key + +tests: +- Integration: Test category navigation with keyboard +- Manual: Navigate categories with arrow keys +- Edge case: Test category navigation from shows list + +acceptance_criteria: +- Arrow keys navigate categories correctly +- Category index updates on navigation +- Enter key selects category and updates shows list + +validation: +- Use arrow keys to navigate categories +- Verify category highlight moves correctly +- Press Enter to select category and verify show list updates + +notes: +- Check if categoryIndex signal is bound correctly +- Ensure arrow keys update categoryIndex signal +- Verify categoryIndex is used in filteredPodcasts() +- Test category navigation from shows list back to categories +- Add keyboard hints in UI diff --git a/tasks/discover-categories-fix/README.md b/tasks/discover-categories-fix/README.md new file mode 100644 index 0000000..2cd2fb1 --- /dev/null +++ b/tasks/discover-categories-fix/README.md @@ -0,0 +1,19 @@ +# Discover Categories Shortcuts Fix + +Objective: Fix broken discover category filter functionality + +Status legend: [ ] todo, [~] in-progress, [x] done + +Tasks +- [ ] 20 — Debug category filter implementation → `20-category-filter-debug.md` +- [ ] 21 — Fix category state synchronization → `21-category-state-sync.md` +- [ ] 22 — Fix category keyboard navigation → `22-category-navigation-fix.md` + +Dependencies +- 20 -> 21 +- 21 -> 22 + +Exit criteria +- Category filter correctly updates show list +- Keyboard navigation works for categories +- Category selection persists during navigation diff --git a/tasks/episode-downloads/14-download-storage-structure.md b/tasks/episode-downloads/14-download-storage-structure.md new file mode 100644 index 0000000..22169d8 --- /dev/null +++ b/tasks/episode-downloads/14-download-storage-structure.md @@ -0,0 +1,46 @@ +# 14. Define Download Storage Structure + +meta: + id: episode-downloads-14 + feature: episode-downloads + priority: P2 + depends_on: [] + tags: [storage, types, data-model] + +objective: +- Define data structures for downloaded episodes +- Create download state tracking +- Design download history and metadata storage + +deliverables: +- DownloadedEpisode type definition +- Download state interface +- Storage schema for download metadata + +steps: +1. Add DownloadedEpisode type to types/episode.ts +2. Define download state structure (status, progress, timestamp) +3. Create download metadata interface +4. Add download-related fields to Feed type +5. Design database-like storage structure + +tests: +- Unit: Test type definitions +- Integration: Test storage schema +- Validation: Verify structure supports all download scenarios + +acceptance_criteria: +- DownloadedEpisode type properly defines download metadata +- Download state interface tracks all necessary information +- Storage schema supports history and progress tracking + +validation: +- Review type definitions for completeness +- Verify storage structure can hold all download data +- Test with mock download scenarios + +notes: +- Add fields: status (downloading, completed, failed), progress (0-100), filePath, downloadedAt +- Include download speed and estimated time remaining +- Store download history with timestamps +- Consider adding resume capability diff --git a/tasks/episode-downloads/15-episode-download-utility.md b/tasks/episode-downloads/15-episode-download-utility.md new file mode 100644 index 0000000..c8b526a --- /dev/null +++ b/tasks/episode-downloads/15-episode-download-utility.md @@ -0,0 +1,47 @@ +# 15. Create Episode Download Utility + +meta: + id: episode-downloads-15 + feature: episode-downloads + priority: P2 + depends_on: [episode-downloads-14] + tags: [downloads, utilities, file-io] + +objective: +- Implement episode download functionality +- Download audio files from episode URLs +- Handle download errors and edge cases + +deliverables: +- Download utility function +- File download handler +- Error handling for download failures + +steps: +1. Create `src/utils/episode-downloader.ts` +2. Implement download function using Bun.file() or fetch +3. Add progress tracking during download +4. Handle download cancellation +5. Add error handling for network and file system errors + +tests: +- Unit: Test download function with mock URLs +- Integration: Test with real audio file URLs +- Error handling: Test download failure scenarios + +acceptance_criteria: +- Episodes can be downloaded successfully +- Download progress is tracked +- Errors are handled gracefully + +validation: +- Download test episode from real podcast +- Verify file is saved correctly +- Check download progress tracking + +notes: +- Use Bun's built-in file download capabilities +- Support resuming interrupted downloads +- Handle large files with streaming +- Add download speed tracking +- Consider download location in downloadPath setting diff --git a/tasks/episode-downloads/16-download-progress-tracking.md b/tasks/episode-downloads/16-download-progress-tracking.md new file mode 100644 index 0000000..a7b94e1 --- /dev/null +++ b/tasks/episode-downloads/16-download-progress-tracking.md @@ -0,0 +1,47 @@ +# 16. Implement Download Progress Tracking + +meta: + id: episode-downloads-16 + feature: episode-downloads + priority: P2 + depends_on: [episode-downloads-15] + tags: [progress, state-management, downloads] + +objective: +- Track download progress for each episode +- Update download state in real-time +- Store download progress in persistent storage + +deliverables: +- Download progress state in app store +- Progress update utility +- Integration with download utility + +steps: +1. Add download state to app store +2. Update progress during download +3. Save progress to persistent storage +4. Handle download completion +5. Test progress tracking accuracy + +tests: +- Unit: Test progress update logic +- Integration: Test progress tracking with download +- Persistence: Verify progress saved and restored + +acceptance_criteria: +- Download progress is tracked accurately +- Progress updates in real-time +- Progress persists across app restarts + +validation: +- Download a large file and watch progress +- Verify progress updates at intervals +- Restart app and verify progress restored + +notes: +- Use existing progress store for episode playback +- Create separate download progress store +- Update progress every 1-2 seconds +- Handle download cancellation by resetting progress +- Store progress in XDG_CONFIG_HOME directory diff --git a/tasks/episode-downloads/17-download-ui-component.md b/tasks/episode-downloads/17-download-ui-component.md new file mode 100644 index 0000000..59764dd --- /dev/null +++ b/tasks/episode-downloads/17-download-ui-component.md @@ -0,0 +1,47 @@ +# 17. Add Download Status in Episode List + +meta: + id: episode-downloads-17 + feature: episode-downloads + priority: P2 + depends_on: [episode-downloads-16] + tags: [ui, downloads, display] + +objective: +- Display download status for episodes +- Add download button to episode list +- Show download progress visually + +deliverables: +- Download status indicator component +- Download button in episode list +- Progress bar for downloading episodes + +steps: +1. Add download status field to EpisodeListItem +2. Create download button in MyShowsPage episodes panel +3. Display download status (none, queued, downloading, completed, failed) +4. Add download progress bar for downloading episodes +5. Test download status display + +tests: +- Integration: Test download status display +- Visual: Verify download button and progress bar +- UX: Test download status changes + +acceptance_criteria: +- Download status is visible in episode list +- Download button is accessible +- Progress bar shows download progress + +validation: +- View episode list with download button +- Start download and watch status change +- Verify progress bar updates + +notes: +- Reuse existing episode list UI from MyShowsPage +- Add download icon button next to episode title +- Show status text: "DL", "DWN", "DONE", "ERR" +- Use existing progress bar component for download progress +- Position download button in episode header diff --git a/tasks/episode-downloads/18-auto-download-settings.md b/tasks/episode-downloads/18-auto-download-settings.md new file mode 100644 index 0000000..7da75b6 --- /dev/null +++ b/tasks/episode-downloads/18-auto-download-settings.md @@ -0,0 +1,48 @@ +# 18. Implement Per-Feed Auto-Download Settings + +meta: + id: episode-downloads-18 + feature: episode-downloads + priority: P2 + depends_on: [episode-downloads-17] + tags: [settings, automation, downloads] + +objective: +- Add per-feed auto-download settings +- Configure number of episodes to auto-download per feed +- Enable/disable auto-download per feed + +deliverables: +- Auto-download settings in feed store +- Settings UI for per-feed configuration +- Auto-download trigger logic + +steps: +1. Add autoDownload field to Feed type +2. Add autoDownloadCount field to Feed type +3. Add settings UI in FeedPage or MyShowsPage +4. Implement auto-download trigger logic +5. Test auto-download functionality + +tests: +- Unit: Test auto-download trigger logic +- Integration: Test with multiple feeds +- Edge case: Test with feeds having fewer episodes + +acceptance_criteria: +- Auto-download settings are configurable per feed +- Settings are saved to persistent storage +- Auto-download works correctly when enabled + +validation: +- Configure auto-download for a feed +- Subscribe to new episodes and verify auto-download +- Test with multiple feeds + +notes: +- Add settings in FeedPage or MyShowsPage +- Default: autoDownload = false, autoDownloadCount = 0 +- Only download newest episodes (by pubDate) +- Respect MAX_EPISODES_REFRESH limit +- Add settings in feed detail or feed list +- Consider adding "auto-download all new episodes" setting diff --git a/tasks/episode-downloads/19-download-queue-management.md b/tasks/episode-downloads/19-download-queue-management.md new file mode 100644 index 0000000..24c53c2 --- /dev/null +++ b/tasks/episode-downloads/19-download-queue-management.md @@ -0,0 +1,48 @@ +# 19. Create Download Queue Management + +meta: + id: episode-downloads-19 + feature: episode-downloads + priority: P3 + depends_on: [episode-downloads-18] + tags: [queue, downloads, management] + +objective: +- Manage download queue for multiple episodes +- Handle concurrent downloads +- Provide queue UI for managing downloads + +deliverables: +- Download queue data structure +- Download queue manager +- Download queue UI + +steps: +1. Create download queue data structure +2. Implement download queue manager (add, remove, process) +3. Handle concurrent downloads (limit to 1-2 at a time) +4. Create download queue UI component +5. Test queue management + +tests: +- Unit: Test queue management logic +- Integration: Test with multiple downloads +- Edge case: Test queue with 50+ episodes + +acceptance_criteria: +- Download queue manages multiple downloads +- Concurrent downloads are limited +- Queue UI shows download status + +validation: +- Add 10 episodes to download queue +- Verify queue processes sequentially +- Check queue UI displays correctly + +notes: +- Use queue data structure (array of episodes) +- Limit concurrent downloads to 2 for performance +- Add queue UI in Settings or separate tab +- Show queue in SettingsScreen or new Downloads tab +- Allow removing items from queue +- Add pause/resume for downloads diff --git a/tasks/episode-downloads/README.md b/tasks/episode-downloads/README.md new file mode 100644 index 0000000..cb0b70e --- /dev/null +++ b/tasks/episode-downloads/README.md @@ -0,0 +1,26 @@ +# Episode Downloads + +Objective: Add per-episode download and per-feed auto-download settings + +Status legend: [ ] todo, [~] in-progress, [x] done + +Tasks +- [ ] 14 — Define download storage structure → `14-download-storage-structure.md` +- [ ] 15 — Create episode download utility → `15-episode-download-utility.md` +- [ ] 16 — Implement download progress tracking → `16-download-progress-tracking.md` +- [ ] 17 — Add download status in episode list → `17-download-ui-component.md` +- [ ] 18 — Implement per-feed auto-download settings → `18-auto-download-settings.md` +- [ ] 19 — Create download queue management → `19-download-queue-management.md` + +Dependencies +- 14 -> 15 +- 15 -> 16 +- 16 -> 17 +- 17 -> 18 +- 18 -> 19 + +Exit criteria +- Episodes can be downloaded individually +- Per-feed auto-download settings are configurable +- Download progress is tracked and displayed +- Download queue can be managed diff --git a/tasks/episode-infinite-scroll/10-episode-list-scroll-handler.md b/tasks/episode-infinite-scroll/10-episode-list-scroll-handler.md new file mode 100644 index 0000000..db9eecf --- /dev/null +++ b/tasks/episode-infinite-scroll/10-episode-list-scroll-handler.md @@ -0,0 +1,46 @@ +# 10. Add Scroll Event Listener to Episodes Panel + +meta: + id: episode-infinite-scroll-10 + feature: episode-infinite-scroll + priority: P2 + depends_on: [] + tags: [ui, events, scroll] + +objective: +- Detect when user scrolls to bottom of episodes list +- Add scroll event listener to episodes panel +- Track scroll position and trigger pagination when needed + +deliverables: +- Scroll event handler function +- Scroll position tracking +- Integration with episodes panel + +steps: +1. Modify MyShowsPage to add scroll event listener +2. Detect scroll-to-bottom event (when scrollHeight - scrollTop <= clientHeight) +3. Track current scroll position +4. Add debouncing for scroll events +5. Test scroll detection accuracy + +tests: +- Unit: Test scroll detection logic +- Integration: Test scroll events in episodes panel +- Manual: Scroll to bottom and verify detection + +acceptance_criteria: +- Scroll-to-bottom is detected accurately +- Debouncing prevents excessive event firing +- Scroll position is tracked correctly + +validation: +- Scroll through episodes list +- Verify bottom detection works +- Test with different terminal sizes + +notes: +- Use scrollbox component's scroll event if available +- Debounce scroll events to 100ms +- Handle both manual scroll and programmatic scroll +- Consider virtual scrolling if episode count is large diff --git a/tasks/episode-infinite-scroll/11-paginated-episode-loading.md b/tasks/episode-infinite-scroll/11-paginated-episode-loading.md new file mode 100644 index 0000000..715b225 --- /dev/null +++ b/tasks/episode-infinite-scroll/11-paginated-episode-loading.md @@ -0,0 +1,46 @@ +# 11. Implement Paginated Episode Fetching + +meta: + id: episode-infinite-scroll-11 + feature: episode-infinite-scroll + priority: P2 + depends_on: [episode-infinite-scroll-10] + tags: [rss, pagination, data-fetching] + +objective: +- Fetch episodes in chunks with MAX_EPISODES_REFRESH limit +- Merge new episodes with existing list +- Maintain episode ordering (newest first) + +deliverables: +- Paginated episode fetch function +- Episode list merging logic +- Integration with feed store + +steps: +1. Create paginated fetch function in feed store +2. Implement chunk-based episode fetching (50 episodes at a time) +3. Add logic to merge new episodes with existing list +4. Maintain reverse chronological order (newest first) +5. Deduplicate episodes by title or URL + +tests: +- Unit: Test paginated fetch logic +- Integration: Test with real RSS feeds +- Edge case: Test with feeds having < 50 episodes + +acceptance_criteria: +- Episodes fetched in chunks of MAX_EPISODES_REFRESH +- New episodes merged correctly with existing list +- Episode ordering maintained (newest first) + +validation: +- Test with RSS feed having 100+ episodes +- Verify pagination works correctly +- Check episode ordering after merge + +notes: +- Use existing `MAX_EPISODES_REFRESH = 50` constant +- Add episode deduplication logic +- Preserve episode metadata during merge +- Handle cases where feed has fewer episodes diff --git a/tasks/episode-infinite-scroll/12-episode-list-state-management.md b/tasks/episode-infinite-scroll/12-episode-list-state-management.md new file mode 100644 index 0000000..9fb6447 --- /dev/null +++ b/tasks/episode-infinite-scroll/12-episode-list-state-management.md @@ -0,0 +1,46 @@ +# 12. Manage Episode List Pagination State + +meta: + id: episode-infinite-scroll-12 + feature: episode-infinite-scroll + priority: P2 + depends_on: [episode-infinite-scroll-11] + tags: [state-management, pagination] + +objective: +- Track pagination state (current page, loaded count, has more episodes) +- Manage episode list state changes +- Handle pagination state across component renders + +deliverables: +- Pagination state in feed store +- Episode list state management +- Integration with scroll events + +steps: +1. Add pagination state to feed store (currentPage, loadedCount, hasMore) +2. Update episode list when new episodes are loaded +3. Manage loading state for pagination +4. Handle empty episode list case +5. Test pagination state transitions + +tests: +- Unit: Test pagination state updates +- Integration: Test state transitions with scroll +- Edge case: Test with no episodes in feed + +acceptance_criteria: +- Pagination state accurately tracks loaded episodes +- Episode list updates correctly with new episodes +- Loading state properly managed + +validation: +- Load episodes and verify state updates +- Scroll to bottom and verify pagination triggers +- Test with feed having many episodes + +notes: +- Use existing feed store from `src/stores/feed.ts` +- Add pagination state to Feed interface +- Consider loading indicator visibility +- Handle rapid scroll events gracefully diff --git a/tasks/episode-infinite-scroll/13-load-more-indicator.md b/tasks/episode-infinite-scroll/13-load-more-indicator.md new file mode 100644 index 0000000..c54ecfa --- /dev/null +++ b/tasks/episode-infinite-scroll/13-load-more-indicator.md @@ -0,0 +1,46 @@ +# 13. Add Loading Indicator for Pagination + +meta: + id: episode-infinite-scroll-13 + feature: episode-infinite-scroll + priority: P3 + depends_on: [episode-infinite-scroll-12] + tags: [ui, feedback, loading] + +objective: +- Display loading indicator when fetching more episodes +- Show loading state in episodes panel +- Hide indicator when pagination complete + +deliverables: +- Loading indicator component +- Loading state display logic +- Integration with pagination events + +steps: +1. Add loading state to episodes panel state +2. Create loading indicator UI (spinner or text) +3. Display indicator when fetching episodes +4. Hide indicator when pagination complete +5. Test loading state visibility + +tests: +- Integration: Test loading indicator during fetch +- Visual: Verify loading state doesn't block interaction +- UX: Test loading state disappears when done + +acceptance_criteria: +- Loading indicator displays during fetch +- Indicator is visible but doesn't block scrolling +- Indicator disappears when pagination complete + +validation: +- Scroll to bottom and watch loading indicator +- Verify indicator shows/hides correctly +- Test with slow RSS feeds + +notes: +- Reuse existing loading indicator pattern from MyShowsPage +- Use spinner or "Loading..." text +- Position indicator at bottom of scrollbox +- Don't block user interaction while loading diff --git a/tasks/episode-infinite-scroll/README.md b/tasks/episode-infinite-scroll/README.md new file mode 100644 index 0000000..3a16ee9 --- /dev/null +++ b/tasks/episode-infinite-scroll/README.md @@ -0,0 +1,21 @@ +# Episode List Infinite Scroll + +Objective: Implement scroll-to-bottom loading for episode lists with MAX_EPISODES_REFRESH limit + +Status legend: [ ] todo, [~] in-progress, [x] done + +Tasks +- [ ] 10 — Add scroll event listener to episodes panel → `10-episode-list-scroll-handler.md` +- [ ] 11 — Implement paginated episode fetching → `11-paginated-episode-loading.md` +- [ ] 12 — Manage episode list pagination state → `12-episode-list-state-management.md` +- [ ] 13 — Add loading indicator for pagination → `13-load-more-indicator.md` + +Dependencies +- 10 -> 11 +- 11 -> 12 +- 12 -> 13 + +Exit criteria +- Episode list automatically loads more episodes when scrolling to bottom +- MAX_EPISODES_REFRESH is respected per fetch +- Loading state is properly displayed during pagination diff --git a/tasks/merged-waveform/06-waveform-audio-analysis.md b/tasks/merged-waveform/06-waveform-audio-analysis.md new file mode 100644 index 0000000..bc579a9 --- /dev/null +++ b/tasks/merged-waveform/06-waveform-audio-analysis.md @@ -0,0 +1,46 @@ +# 06. Implement Audio Waveform Analysis + +meta: + id: merged-waveform-06 + feature: merged-waveform + priority: P2 + depends_on: [] + tags: [audio, waveform, analysis] + +objective: +- Analyze audio data to extract waveform information +- Create real-time waveform data from audio streams +- Generate waveform data points for visualization + +deliverables: +- Audio analysis utility +- Waveform data extraction function +- Integration with audio backend + +steps: +1. Research and select audio waveform analysis library (e.g., `audiowaveform`) +2. Create `src/utils/audio-waveform.ts` +3. Implement audio data extraction from backend +4. Generate waveform data points (amplitude values) +5. Add sample rate and duration normalization + +tests: +- Unit: Test waveform generation from sample audio +- Integration: Test with real audio playback +- Performance: Measure waveform generation overhead + +acceptance_criteria: +- Waveform data is generated from audio content +- Data points represent audio amplitude accurately +- Generation works with real-time audio streams + +validation: +- Generate waveform from sample MP3 file +- Verify amplitude data matches audio peaks +- Test with different audio formats + +notes: +- Consider using `ffmpeg` or `sox` for offline analysis +- For real-time: analyze audio chunks during playback +- Waveform resolution: 64-256 data points for TUI display +- Normalize amplitude to 0-1 range diff --git a/tasks/merged-waveform/07-merged-waveform-component.md b/tasks/merged-waveform/07-merged-waveform-component.md new file mode 100644 index 0000000..1a0f69f --- /dev/null +++ b/tasks/merged-waveform/07-merged-waveform-component.md @@ -0,0 +1,46 @@ +# 07. Create Merged Progress-Waveform Component + +meta: + id: merged-waveform-07 + feature: merged-waveform + priority: P2 + depends_on: [merged-waveform-06] + tags: [ui, waveform, component] + +objective: +- Design and implement a single component that shows progress bar and waveform +- Component starts as progress bar, expands to waveform when playing +- Provide smooth transitions between states + +deliverables: +- MergedWaveform component +- State management for progress vs waveform display +- Visual styling for progress bar and waveform + +steps: +1. Create `src/components/MergedWaveform.tsx` +2. Design component state machine (progress bar → waveform) +3. Implement progress bar visualization +4. Add waveform expansion animation +5. Style progress bar and waveform with theme colors + +tests: +- Unit: Test component state transitions +- Integration: Test component in Player +- Visual: Verify smooth expansion animation + +acceptance_criteria: +- Component displays progress bar when paused +- Component smoothly expands to waveform when playing +- Visual styles match theme and existing UI + +validation: +- Test with paused and playing states +- Verify expansion is smooth and visually appealing +- Check theme color integration + +notes: +- Use existing Waveform component as base +- Add CSS transitions for smooth expansion +- Keep component size manageable (fit in progress bar area) +- Consider responsive to terminal width changes diff --git a/tasks/merged-waveform/08-realtime-waveform-rendering.md b/tasks/merged-waveform/08-realtime-waveform-rendering.md new file mode 100644 index 0000000..12077b0 --- /dev/null +++ b/tasks/merged-waveform/08-realtime-waveform-rendering.md @@ -0,0 +1,46 @@ +# 08. Implement Real-Time Waveform Rendering During Playback + +meta: + id: merged-waveform-08 + feature: merged-waveform + priority: P2 + depends_on: [merged-waveform-07] + tags: [audio, realtime, rendering] + +objective: +- Update waveform in real-time during audio playback +- Highlight waveform based on current playback position +- Sync waveform with audio backend position updates + +deliverables: +- Real-time waveform update logic +- Playback position highlighting +- Integration with audio backend position tracking + +steps: +1. Subscribe to audio backend position updates +2. Update waveform data points based on playback position +3. Implement playback position highlighting +4. Add animation for progress indicator +5. Test synchronization with audio playback + +tests: +- Integration: Test waveform sync with audio playback +- Performance: Measure real-time update overhead +- Visual: Verify progress highlighting matches audio position + +acceptance_criteria: +- Waveform updates in real-time during playback +- Playback position is accurately highlighted +- No lag or desynchronization with audio + +validation: +- Play audio and watch waveform update +- Verify progress bar matches audio position +- Test with different playback speeds + +notes: +- Use existing audio position polling in `useAudio.ts` +- Update waveform every ~100ms for smooth visuals +- Consider reducing waveform resolution during playback for performance +- Ensure highlighting doesn't flicker diff --git a/tasks/merged-waveform/09-waveform-performance-optimization.md b/tasks/merged-waveform/09-waveform-performance-optimization.md new file mode 100644 index 0000000..e59c2a4 --- /dev/null +++ b/tasks/merged-waveform/09-waveform-performance-optimization.md @@ -0,0 +1,46 @@ +# 09. Optimize Waveform Rendering Performance + +meta: + id: merged-waveform-09 + feature: merged-waveform + priority: P3 + depends_on: [merged-waveform-08] + tags: [performance, optimization] + +objective: +- Ensure waveform rendering doesn't cause performance issues +- Optimize for terminal TUI environment +- Minimize CPU and memory usage + +deliverables: +- Performance optimizations +- Memory management for waveform data +- Performance monitoring and testing + +steps: +1. Profile waveform rendering performance +2. Optimize data point generation and updates +3. Implement waveform data caching +4. Add performance monitoring +5. Test with long audio files + +tests: +- Performance: Measure CPU usage during playback +- Performance: Measure memory usage over time +- Load test: Test with 30+ minute audio files + +acceptance_criteria: +- Waveform rendering < 16ms per frame +- No memory leaks during extended playback +- Smooth playback even with waveform rendering + +validation: +- Profile CPU usage during playback +- Monitor memory over 30-minute playback session +- Test with multiple simultaneous audio files + +notes: +- Consider reducing waveform resolution during playback +- Cache waveform data to avoid regeneration +- Use efficient data structures for waveform points +- Test on slower terminals (e.g., tmux) diff --git a/tasks/merged-waveform/README.md b/tasks/merged-waveform/README.md new file mode 100644 index 0000000..8b66b7b --- /dev/null +++ b/tasks/merged-waveform/README.md @@ -0,0 +1,21 @@ +# Merged Waveform Progress Bar + +Objective: Create a real-time waveform visualization that expands from a progress bar during playback + +Status legend: [ ] todo, [~] in-progress, [x] done + +Tasks +- [ ] 06 — Implement audio waveform analysis → `06-waveform-audio-analysis.md` +- [ ] 07 — Create merged progress-waveform component → `07-merged-waveform-component.md` +- [ ] 08 — Implement real-time waveform rendering during playback → `08-realtime-waveform-rendering.md` +- [ ] 09 — Optimize waveform rendering performance → `09-waveform-performance-optimization.md` + +Dependencies +- 06 -> 07 +- 07 -> 08 +- 08 -> 09 + +Exit criteria +- Waveform smoothly expands from progress bar during playback +- Waveform is highlighted based on current playback position +- No performance degradation during playback diff --git a/tasks/podcast-tui-app/01-project-setup.md b/tasks/podcast-tui-app/01-project-setup.md deleted file mode 100644 index d2b308f..0000000 --- a/tasks/podcast-tui-app/01-project-setup.md +++ /dev/null @@ -1,51 +0,0 @@ -# 01. Initialize SolidJS OpenTUI Project with Bun - -meta: - id: podcast-tui-app-01 - feature: podcast-tui-app - priority: P0 - depends_on: [] - tags: [project-setup, solidjs, opentui, bun] - -objective: -- Initialize a new SolidJS-based OpenTUI project using Bun -- Set up project structure with all necessary dependencies -- Configure TypeScript and development environment - -deliverables: -- `/podcast-tui-app/` directory created -- `package.json` with all dependencies -- `tsconfig.json` configured for TypeScript -- `bunfig.toml` with Bun configuration -- `.gitignore` for Git version control -- `README.md` with project description - -steps: -- Run `bunx create-tui@latest -t solid podcast-tui-app` to initialize the project -- Verify project structure is created correctly -- Install additional dependencies: `bun add @opentui/solid @opentui/core solid-js zustand` -- Install dev dependencies: `bun add -d @opentui/testing @opentui/react @opentui/solid` -- Configure TypeScript in `tsconfig.json` with SolidJS and OpenTUI settings -- Create `.gitignore` with Node and Bun specific files -- Initialize Git repository: `git init` - -tests: -- Unit: Verify `create-tui` command works without errors -- Integration: Test that application can be started with `bun run src/index.tsx` -- Environment: Confirm all dependencies are installed correctly - -acceptance_criteria: -- Project directory `podcast-tui-app/` exists -- `package.json` contains `@opentui/solid` and `@opentui/core` dependencies -- `bun run src/index.tsx` starts the application without errors -- TypeScript compilation works with `bun run build` - -validation: -- Run `bun run build` to verify TypeScript compilation -- Run `bun run src/index.tsx` to start the application -- Run `bun pm ls` to verify all dependencies are installed - -notes: -- OpenTUI is already installed in the system -- Use `-t solid` flag for SolidJS template -- All commands should be run from the project root diff --git a/tasks/podcast-tui-app/02-core-layout.md b/tasks/podcast-tui-app/02-core-layout.md deleted file mode 100644 index ea9ee02..0000000 --- a/tasks/podcast-tui-app/02-core-layout.md +++ /dev/null @@ -1,55 +0,0 @@ -# 02. Create Main App Shell with Tab Navigation - -meta: - id: podcast-tui-app-02 - feature: podcast-tui-app - priority: P0 - depends_on: [01] - tags: [layout, navigation, solidjs, opentui] - -objective: -- Create the main application shell with a responsive layout -- Implement tab-based navigation system -- Set up the root component structure -- Configure Flexbox layout for terminal UI - -deliverables: -- `src/App.tsx` with main app shell -- `src/components/Navigation.tsx` with tab navigation -- `src/components/Layout.tsx` with responsive layout -- Tab navigation component with 5 tabs: Discover, My Feeds, Search, Player, Settings - -steps: -- Create `src/App.tsx` with main component that renders Navigation -- Create `src/components/Navigation.tsx` with tab navigation - - Use `` with `` for navigation - - Implement tab selection state with `createSignal` - - Add keyboard navigation (arrow keys) - - Add tab labels: "Discover", "My Feeds", "Search", "Player", "Settings" -- Create `src/components/Layout.tsx` with responsive layout - - Use Flexbox with `flexDirection="column"` - - Create top navigation bar - - Create main content area - - Handle terminal resizing -- Update `src/index.tsx` to render the app - -tests: -- Unit: Test Navigation component renders with correct tabs -- Integration: Test keyboard navigation moves between tabs -- Component: Verify layout adapts to terminal size changes - -acceptance_criteria: -- Navigation component displays 5 tabs correctly -- Tab selection is visually indicated -- Arrow keys navigate between tabs -- Layout fits within terminal bounds - -validation: -- Run application and verify navigation appears -- Press arrow keys to test navigation -- Resize terminal and verify layout adapts - -notes: -- Use SolidJS `createSignal` for state management -- Follow OpenTUI layout patterns from `layout/REFERENCE.md` -- Navigation should be persistent across all screens diff --git a/tasks/podcast-tui-app/03-file-sync.md b/tasks/podcast-tui-app/03-file-sync.md deleted file mode 100644 index bf6e4fd..0000000 --- a/tasks/podcast-tui-app/03-file-sync.md +++ /dev/null @@ -1,64 +0,0 @@ -# 03. Implement Direct File Sync (JSON/XML Import/Export) - -meta: - id: podcast-tui-app-03 - feature: podcast-tui-app - priority: P1 - depends_on: [02] - tags: [file-sync, json, xml, import-export, solidjs] - -objective: -- Create data models for JSON and XML sync formats -- Implement import functionality to load feeds and settings from files -- Implement export functionality to save feeds and settings to files -- Add file picker UI for selecting files - -deliverables: -- `src/types/sync.ts` with sync data models -- `src/utils/sync.ts` with import/export functions -- `src/components/SyncPanel.tsx` with sync UI -- File picker component for file selection - -steps: -- Create `src/types/sync.ts` with data models: - - `SyncData` interface for JSON format - - `SyncDataXML` interface for XML format - - Include fields: feeds, sources, settings, preferences -- Create `src/utils/sync.ts` with functions: - - `exportToJSON(data: SyncData): string` - - `importFromJSON(json: string): SyncData` - - `exportToXML(data: SyncDataXML): string` - - `importFromXML(xml: string): SyncDataXML` - - Handle validation and error checking -- Create `src/components/SyncPanel.tsx` with: - - Import button - - Export button - - File picker UI using `` component - - Sync status indicator -- Add sync functionality to Settings screen - -tests: -- Unit: Test JSON import/export with sample data -- Unit: Test XML import/export with sample data -- Integration: Test file picker selects correct files -- Integration: Test sync panel buttons work correctly - -acceptance_criteria: -- Export creates valid JSON file with all data -- Export creates valid XML file with all data -- Import loads data from JSON file -- Import loads data from XML file -- File picker allows selecting files from disk - -validation: -- Run `bun run src/index.tsx` -- Go to Settings > Sync -- Click Export, verify file created -- Click Import, select file, verify data loaded -- Test with both JSON and XML formats - -notes: -- JSON format: Simple, human-readable -- XML format: More structured, better for complex data -- Use `FileReader` API for file operations -- Handle file not found and invalid format errors diff --git a/tasks/podcast-tui-app/04-authentication.md b/tasks/podcast-tui-app/04-authentication.md deleted file mode 100644 index 10a3724..0000000 --- a/tasks/podcast-tui-app/04-authentication.md +++ /dev/null @@ -1,78 +0,0 @@ -# 04. Build Optional Authentication System - -meta: - id: podcast-tui-app-04 - feature: podcast-tui-app - priority: P2 - depends_on: [03] - tags: [authentication, optional, solidjs, security] - -objective: -- Create authentication state management (disabled by default) -- Build simple login screen with email/password -- Implement 8-character code validation flow -- Add OAuth placeholder screens -- Create sync-only user profile - -deliverables: -- `src/stores/auth.ts` with authentication state store -- `src/components/LoginScreen.tsx` with login form -- `src/components/CodeValidation.tsx` with 8-character code input -- `src/components/OAuthPlaceholder.tsx` with OAuth info -- `src/components/SyncProfile.tsx` with sync-only profile - -steps: -- Create `src/stores/auth.ts` with Zustand store: - - `user` state (initially null) - - `isAuthenticated` state (initially false) - - `login()` function - - `logout()` function - - `validateCode()` function -- Create `src/components/LoginScreen.tsx`: - - Email input field - - Password input field - - Login button - - Link to code validation flow - - Link to OAuth placeholder -- Create `src/components/CodeValidation.tsx`: - - 8-character code input - - Validation logic (alphanumeric) - - Submit button - - Error message display -- Create `src/components/OAuthPlaceholder.tsx`: - - Display OAuth information - - Explain terminal limitations - - Link to browser redirect flow -- Create `src/components/SyncProfile.tsx`: - - User profile display - - Sync status indicator - - Profile management options -- Add auth screens to Navigation (hidden by default) - -tests: -- Unit: Test authentication state management -- Unit: Test code validation logic -- Integration: Test login flow completes -- Integration: Test logout clears state - -acceptance_criteria: -- Authentication is disabled by default (isAuthenticated = false) -- Login screen accepts email and password -- Code validation accepts 8-character codes -- OAuth placeholder displays limitations -- Sync profile shows user info -- Login state persists across sessions - -validation: -- Run application and verify login screen is accessible -- Try to log in with valid credentials -- Try to log in with invalid credentials -- Test code validation with valid/invalid codes -- Verify authentication state persists - -notes: -- Authentication is optional and disabled by default -- Focus on file sync, not user accounts -- Use simple validation (no real backend) -- Store authentication state in localStorage -- OAuth not feasible in terminal, document limitation diff --git a/tasks/podcast-tui-app/05-feed-management.md b/tasks/podcast-tui-app/05-feed-management.md deleted file mode 100644 index c04c1fb..0000000 --- a/tasks/podcast-tui-app/05-feed-management.md +++ /dev/null @@ -1,58 +0,0 @@ -# 05. Create Feed Data Models and Types - -meta: - id: podcast-tui-app-05 - feature: podcast-tui-app - priority: P0 - depends_on: [04] - tags: [types, data-models, solidjs, typescript] - -objective: -- Define TypeScript interfaces for all podcast-related data types -- Create models for feeds, episodes, sources, and user preferences -- Set up type definitions for sync functionality - -deliverables: -- `src/types/podcast.ts` with all data models -- `src/types/episode.ts` with episode-specific types -- `src/types/source.ts` with podcast source types -- `src/types/preference.ts` with user preference types - -steps: -- Create `src/types/podcast.ts` with core types: - - `Podcast` interface (id, title, description, coverUrl, feedUrl, lastUpdated) - - `Episode` interface (id, title, description, audioUrl, duration, pubDate, episodeNumber) - - `Feed` interface (id, podcast, episodes[], isPublic, sourceId) - - `FeedItem` interface (represents a single episode in a feed) -- Create `src/types/episode.ts` with episode types: - - `Episode` interface - - `EpisodeStatus` enum (playing, paused, completed) - - `Progress` interface (episodeId, position, duration) -- Create `src/types/source.ts` with source types: - - `PodcastSource` interface (id, name, baseUrl, type, apiKey) - - `SourceType` enum (rss, api, custom) - - `SearchQuery` interface (query, sourceIds, filters) -- Create `src/types/preference.ts` with preference types: - - `UserPreference` interface (theme, fontSize, playbackSpeed, autoDownload) - - `SyncPreference` interface (autoSync, backupInterval, syncMethod) -- Add type exports in `src/index.ts` - -tests: -- Unit: Verify all interfaces compile correctly -- Unit: Test enum values are correct -- Integration: Test type definitions match expected data structures - -acceptance_criteria: -- All TypeScript interfaces compile without errors -- Types are exported for use across the application -- Type definitions cover all podcast-related data - -validation: -- Run `bun run build` to verify TypeScript compilation -- Check `src/types/` directory contains all required files - -notes: -- Use strict TypeScript mode -- Include JSDoc comments for complex types -- Keep types simple and focused -- Ensure types are compatible with sync JSON/XML formats diff --git a/tasks/podcast-tui-app/06-search.md b/tasks/podcast-tui-app/06-search.md deleted file mode 100644 index 19d8da1..0000000 --- a/tasks/podcast-tui-app/06-search.md +++ /dev/null @@ -1,63 +0,0 @@ -# 06. Build Feed List Component (Public/Private Feeds) - -meta: - id: podcast-tui-app-06 - feature: podcast-tui-app - priority: P0 - depends_on: [05] - tags: [feed-list, components, solidjs, opentui] - -objective: -- Create a scrollable feed list component -- Display public and private feeds -- Implement feed selection and display -- Add reverse chronological ordering - -deliverables: -- `src/components/FeedList.tsx` with feed list component -- `src/components/FeedItem.tsx` with individual feed item -- `src/components/FeedFilter.tsx` with public/private toggle - -steps: -- Create `src/components/FeedList.tsx`: - - Use `` for scrollable list - - Accept feeds array as prop - - Implement feed rendering with `createSignal` for selection - - Add keyboard navigation (arrow keys, enter) - - Display feed title, description, episode count -- Create `src/components/FeedItem.tsx`: - - Display feed information - - Show public/private indicator - - Highlight selected feed - - Add hover effects -- Create `src/components/FeedFilter.tsx`: - - Toggle button for public/private feeds - - Filter logic implementation - - Update parent FeedList when filtered -- Add feed list to "My Feeds" navigation tab - -tests: -- Unit: Test FeedList renders with feeds -- Unit: Test FeedItem displays correctly -- Integration: Test public/private filtering -- Integration: Test keyboard navigation in feed list - -acceptance_criteria: -- Feed list displays all feeds correctly -- Public/private toggle filters feeds -- Feed selection is visually indicated -- Keyboard navigation works (arrow keys, enter) -- List scrolls properly when many feeds - -validation: -- Run application and navigate to "My Feeds" -- Add some feeds and verify they appear -- Test public/private toggle -- Use arrow keys to navigate feeds -- Scroll list with many feeds - -notes: -- Use SolidJS `createSignal` for selection state -- Follow OpenTUI component patterns from `components/REFERENCE.md` -- Feed list should be scrollable with many items -- Use Flexbox for layout diff --git a/tasks/podcast-tui-app/07-discover.md b/tasks/podcast-tui-app/07-discover.md deleted file mode 100644 index 58e1d9c..0000000 --- a/tasks/podcast-tui-app/07-discover.md +++ /dev/null @@ -1,68 +0,0 @@ -# 07. Implement Multi-Source Search Interface - -meta: - id: podcast-tui-app-07 - feature: podcast-tui-app - priority: P1 - depends_on: [06] - tags: [search, multi-source, solidjs, opentui] - -objective: -- Create search input component -- Implement multi-source search functionality -- Display search results with sources -- Add search history with persistent storage - -deliverables: -- `src/components/SearchBar.tsx` with search input -- `src/components/SearchResults.tsx` with results display -- `src/components/SearchHistory.tsx` with history list -- `src/utils/search.ts` with search logic - -steps: -- Create `src/components/SearchBar.tsx`: - - Search input field using `` component - - Search button - - Clear history button - - Enter key handler -- Create `src/utils/search.ts`: - - `searchPodcasts(query: string, sourceIds: string[]): Promise` - - `searchEpisodes(query: string, feedId: string): Promise` - - Handle multiple sources - - Cache search results -- Create `src/components/SearchResults.tsx`: - - Display search results with source indicators - - Show podcast/episode info - - Add click to add to feeds - - Keyboard navigation through results -- Create `src/components/SearchHistory.tsx`: - - Display recent search queries - - Click to re-run search - - Delete individual history items - - Persist to localStorage - -tests: -- Unit: Test search logic returns correct results -- Unit: Test search history persistence -- Integration: Test search bar accepts input -- Integration: Test results display correctly - -acceptance_criteria: -- Search bar accepts and processes queries -- Multi-source search works across all enabled sources -- Search results display with source information -- Search history persists across sessions -- Keyboard navigation works in results - -validation: -- Run application and navigate to "Search" -- Type a query and press Enter -- Verify results appear -- Click a result to add to feed -- Restart app and verify history persists - -notes: -- Use localStorage for search history -- Implement basic caching to avoid repeated searches -- Handle empty results gracefully -- Add loading state during search diff --git a/tasks/podcast-tui-app/08-player.md b/tasks/podcast-tui-app/08-player.md deleted file mode 100644 index 5e4b675..0000000 --- a/tasks/podcast-tui-app/08-player.md +++ /dev/null @@ -1,63 +0,0 @@ -# 08. Build Discover Feed with Popular Shows - -meta: - id: podcast-tui-app-08 - feature: podcast-tui-app - priority: P1 - depends_on: [07] - tags: [discover, popular-shows, solidjs, opentui] - -objective: -- Create popular shows data structure -- Build discover page component -- Display trending shows with categories -- Implement category filtering - -deliverables: -- `src/data/popular-shows.ts` with trending shows data -- `src/components/DiscoverPage.tsx` with discover UI -- `src/components/CategoryFilter.tsx` with category buttons - -steps: -- Create `src/data/popular-shows.ts`: - - Array of popular podcasts with metadata - - Categories: Technology, Business, Science, Entertainment - - Reverse chronological ordering (newest first) - - Include feed URLs and descriptions -- Create `src/components/DiscoverPage.tsx`: - - Title header - - Category filter buttons - - Grid/list display of popular shows - - Show details on selection - - Add to feed button -- Create `src/components/CategoryFilter.tsx`: - - Category button group - - Active category highlighting - - Filter logic implementation -- Add discover page to "Discover" navigation tab - -tests: -- Unit: Test popular shows data structure -- Unit: Test category filtering -- Integration: Test discover page displays correctly -- Integration: Test add to feed functionality - -acceptance_criteria: -- Discover page displays popular shows -- Category filtering works correctly -- Shows are ordered reverse chronologically -- Clicking a show shows details -- Add to feed button works - -validation: -- Run application and navigate to "Discover" -- Verify popular shows appear -- Click different categories -- Click a show and verify details -- Try add to feed - -notes: -- Popular shows data can be static or fetched from sources -- If sources don't provide trending, use curated list -- Categories help users find shows by topic -- Use Flexbox for category filter layout diff --git a/tasks/podcast-tui-app/09-settings.md b/tasks/podcast-tui-app/09-settings.md deleted file mode 100644 index 70303a4..0000000 --- a/tasks/podcast-tui-app/09-settings.md +++ /dev/null @@ -1,70 +0,0 @@ -# 09. Create Player UI with Waveform Visualization - -meta: - id: podcast-tui-app-09 - feature: podcast-tui-app - priority: P0 - depends_on: [08] - tags: [player, waveform, visualization, solidjs, opentui] - -objective: -- Create player UI layout -- Implement playback controls (play/pause, skip, progress) -- Build ASCII waveform visualization -- Add progress tracking and seek functionality - -deliverables: -- `src/components/Player.tsx` with player UI -- `src/components/PlaybackControls.tsx` with controls -- `src/components/Waveform.tsx` with ASCII waveform -- `src/utils/waveform.ts` with visualization logic - -steps: -- Create `src/components/Player.tsx`: - - Player header with episode info - - Progress bar with seek functionality - - Waveform visualization area - - Playback controls -- Create `src/components/PlaybackControls.tsx`: - - Play/Pause button - - Previous/Next episode buttons - - Volume control - - Speed control - - Keyboard shortcuts (space, arrows) -- Create `src/components/Waveform.tsx`: - - ASCII waveform visualization - - Click to seek - - Color-coded for played/paused - - Use frame buffer for drawing -- Create `src/utils/waveform.ts`: - - Generate waveform data from audio - - Convert to ASCII characters - - Handle audio duration and position -- Add player to "Player" navigation tab - -tests: -- Unit: Test waveform generation -- Unit: Test playback controls -- Integration: Test player UI displays correctly -- Integration: Test keyboard shortcuts work - -acceptance_criteria: -- Player UI displays episode information -- Playback controls work (play/pause, skip) -- Waveform visualization shows audio waveform -- Progress bar updates during playback -- Clicking waveform seeks to position - -validation: -- Run application and navigate to "Player" -- Select an episode to play -- Test playback controls -- Verify waveform visualization -- Test seeking by clicking waveform -- Test keyboard shortcuts - -notes: -- ASCII waveform: Use `#` for peaks, `.` for valleys -- Audio integration: Trigger system player or use Web Audio API -- Waveform data needs to be cached for performance -- Use SolidJS `useTimeline` for animation diff --git a/tasks/podcast-tui-app/10-state-management.md b/tasks/podcast-tui-app/10-state-management.md deleted file mode 100644 index 9e3cfa6..0000000 --- a/tasks/podcast-tui-app/10-state-management.md +++ /dev/null @@ -1,70 +0,0 @@ -# 10. Build Settings Screen and Preferences - -meta: - id: podcast-tui-app-10 - feature: podcast-tui-app - priority: P1 - depends_on: [09] - tags: [settings, preferences, solidjs, opentui] - -objective: -- Create settings screen component -- Add source management UI -- Build user preferences panel -- Implement data persistence - -deliverables: -- `src/components/SettingsScreen.tsx` with settings UI -- `src/components/SourceManager.tsx` with source management -- `src/components/PreferencesPanel.tsx` with user preferences -- `src/utils/persistence.ts` with localStorage utilities - -steps: -- Create `src/components/SettingsScreen.tsx`: - - Settings menu with sections - - Navigation between settings sections - - Save/Cancel buttons -- Create `src/components/SourceManager.tsx`: - - List of enabled sources - - Add source button - - Remove source button - - Enable/disable toggle -- Create `src/components/PreferencesPanel.tsx`: - - Theme selection (light/dark) - - Font size control - - Playback speed control - - Auto-download settings -- Create `src/utils/persistence.ts`: - - `savePreference(key, value)` - - `loadPreference(key)` - - `saveFeeds(feeds)` - - `loadFeeds()` - - `saveSettings(settings)` - - `loadSettings()` -- Add settings screen to "Settings" navigation tab - -tests: -- Unit: Test persistence functions -- Unit: Test source management -- Integration: Test settings screen navigation -- Integration: Test preferences save/load - -acceptance_criteria: -- Settings screen displays all sections -- Source management adds/removes sources -- Preferences save correctly -- Data persists across sessions -- Settings screen is accessible - -validation: -- Run application and navigate to "Settings" -- Test source management -- Change preferences and verify save -- Restart app and verify preferences persist - -notes: -- Use localStorage for simple persistence -- Settings are application-level, not user-specific -- Source management requires API keys if needed -- Preferences are per-user -- Add error handling for persistence failures diff --git a/tasks/podcast-tui-app/11-testing.md b/tasks/podcast-tui-app/11-testing.md deleted file mode 100644 index 64b0866..0000000 --- a/tasks/podcast-tui-app/11-testing.md +++ /dev/null @@ -1,68 +0,0 @@ -# 11. Create Global State Store and Data Layer - -meta: - id: podcast-tui-app-11 - feature: podcast-tui-app - priority: P0 - depends_on: [10] - tags: [state-management, global-store, signals, solidjs] - -objective: -- Create global state store using Signals -- Implement data fetching and caching -- Build file-based storage for sync -- Connect all components to shared state - -deliverables: -- `src/stores/appStore.ts` with global state store -- `src/stores/feedStore.ts` with feed state -- `src/stores/playerStore.ts` with player state -- `src/stores/searchStore.ts` with search state -- `src/utils/storage.ts` with file-based storage - -steps: -- Create `src/stores/appStore.ts`: - - Use SolidJS signals for global state - - Store application state: currentTab, isAuthEnabled, settings - - Provide state to all child components -- Create `src/stores/feedStore.ts`: - - Signals for feeds array - - Signals for selectedFeed - - Methods: addFeed, removeFeed, updateFeed -- Create `src/stores/playerStore.ts`: - - Signals for currentEpisode - - Signals for playbackState - - Methods: play, pause, seek, setSpeed -- Create `src/stores/searchStore.ts`: - - Signals for searchResults - - Signals for searchHistory - - Methods: search, addToHistory, clearHistory -- Create `src/utils/storage.ts`: - - `saveToLocal()` - - `loadFromLocal()` - - File-based sync for feeds and settings - -tests: -- Unit: Test store methods update signals correctly -- Unit: Test storage functions -- Integration: Test state persists across components -- Integration: Test data sync with file storage - -acceptance_criteria: -- Global state store manages all app state -- Store methods update signals correctly -- State persists across component re-renders -- File-based storage works for sync - -validation: -- Run application and verify state is initialized -- Modify state and verify UI updates -- Restart app and verify state persistence -- Test sync functionality - -notes: -- Use SolidJS `createSignal` for reactivity -- Store should be singleton pattern -- Use Zustand if complex state management needed -- Keep store simple and focused -- File-based storage for sync with JSON/XML diff --git a/tasks/podcast-tui-app/12-optimization.md b/tasks/podcast-tui-app/12-optimization.md deleted file mode 100644 index 37b02f3..0000000 --- a/tasks/podcast-tui-app/12-optimization.md +++ /dev/null @@ -1,68 +0,0 @@ -# 12. Set Up Testing Framework and Write Tests - -meta: - id: podcast-tui-app-12 - feature: podcast-tui-app - priority: P1 - depends_on: [11] - tags: [testing, snapshot-testing, solidjs, opentui] - -objective: -- Set up OpenTUI testing framework -- Write component tests for all major components -- Add keyboard interaction tests -- Implement error handling tests - -deliverables: -- `tests/` directory with test files -- `tests/components/` with component tests -- `tests/integration/` with integration tests -- `tests/utils/` with utility tests -- Test coverage for all components - -steps: -- Set up OpenTUI testing framework: - - Install testing dependencies - - Configure test runner - - Set up snapshot testing -- Write component tests: - - `tests/components/Navigation.test.tsx` - - `tests/components/FeedList.test.tsx` - - `tests/components/SearchBar.test.tsx` - - `tests/components/Player.test.tsx` - - `tests/components/SettingsScreen.test.tsx` -- Write integration tests: - - `tests/integration/navigation.test.tsx` - - `tests/integration/feed-management.test.tsx` - - `tests/integration/search.test.tsx` -- Write utility tests: - - `tests/utils/sync.test.ts` - - `tests/utils/search.test.ts` - - `tests/utils/storage.test.ts` -- Add error handling tests: - - Test invalid file imports - - Test network errors - - Test malformed data - -tests: -- Unit: Run all unit tests -- Integration: Run all integration tests -- Coverage: Verify all components tested - -acceptance_criteria: -- All tests pass -- Test coverage > 80% -- Snapshot tests match expected output -- Error handling tests verify proper behavior - -validation: -- Run `bun test` to execute all tests -- Run `bun test --coverage` for coverage report -- Fix any failing tests - -notes: -- Use OpenTUI's testing framework for snapshot testing -- Test keyboard interactions separately -- Mock external dependencies (API calls) -- Keep tests fast and focused -- Add CI/CD integration for automated testing diff --git a/tasks/podcast-tui-app/13-typescript-config.md b/tasks/podcast-tui-app/13-typescript-config.md deleted file mode 100644 index 84bea73..0000000 --- a/tasks/podcast-tui-app/13-typescript-config.md +++ /dev/null @@ -1,63 +0,0 @@ -# 13. Set Up TypeScript Configuration and Build System - -meta: - id: podcast-tui-app-13 - feature: podcast-tui-app - priority: P0 - depends_on: [01] - tags: [typescript, build-system, configuration, solidjs] - -objective: -- Configure TypeScript for SolidJS and OpenTUI -- Set up build system for production -- Configure bundler for optimal output -- Add development and production scripts - -deliverables: -- `tsconfig.json` with TypeScript configuration -- `bunfig.toml` with Bun configuration -- `package.json` with build scripts -- `.bunfig.toml` with build settings - -steps: -- Configure TypeScript in `tsconfig.json`: - - Set target to ES2020 - - Configure paths for SolidJS - - Add OpenTUI type definitions - - Enable strict mode - - Configure module resolution -- Configure Bun in `bunfig.toml`: - - Set up dependencies - - Configure build output - - Add optimization flags -- Update `package.json`: - - Add build script: `bun run build` - - Add dev script: `bun run dev` - - Add test script: `bun run test` - - Add lint script: `bun run lint` -- Configure bundler for production: - - Optimize bundle size - - Minify output - - Tree-shake unused code - -tests: -- Unit: Verify TypeScript configuration -- Integration: Test build process -- Integration: Test dev script - -acceptance_criteria: -- TypeScript compiles without errors -- Build script creates optimized bundle -- Dev script runs development server -- All scripts work correctly - -validation: -- Run `bun run build` to verify build -- Run `bun run dev` to verify dev server -- Check bundle size is reasonable - -notes: -- Use `bun build` for production builds -- Enable source maps for debugging -- Configure TypeScript to match OpenTUI requirements -- Add path aliases for cleaner imports diff --git a/tasks/podcast-tui-app/15-responsive-layout.md b/tasks/podcast-tui-app/15-responsive-layout.md deleted file mode 100644 index 7f14c99..0000000 --- a/tasks/podcast-tui-app/15-responsive-layout.md +++ /dev/null @@ -1,70 +0,0 @@ -# 14. Create Project Directory Structure and Dependencies - -meta: - id: podcast-tui-app-14 - feature: podcast-tui-app - priority: P0 - depends_on: [01] - tags: [project-structure, organization, solidjs] - -objective: -- Create organized project directory structure -- Set up all necessary folders and files -- Install and configure all dependencies -- Create placeholder files for future implementation - -deliverables: -- `src/` directory with organized structure -- `src/components/` with component folders -- `src/stores/` with store files -- `src/types/` with type definitions -- `src/utils/` with utility functions -- `src/data/` with static data -- `tests/` with test structure -- `public/` for static assets -- `docs/` for documentation - -steps: -- Create directory structure: - - `src/components/` (all components) - - `src/stores/` (state management) - - `src/types/` (TypeScript types) - - `src/utils/` (utility functions) - - `src/data/` (static data) - - `src/hooks/` (custom hooks) - - `src/api/` (API clients) - - `tests/` (test files) - - `public/` (static assets) - - `docs/` (documentation) -- Create placeholder files: - - `src/index.tsx` (main entry) - - `src/App.tsx` (app shell) - - `src/main.ts` (Bun entry) -- Install dependencies: - - Core: `@opentui/solid`, `@opentui/core`, `solid-js` - - State: `zustand` - - Testing: `@opentui/testing` - - Utilities: `date-fns`, `uuid` - - Optional: `react`, `@opentui/react` (for testing) - -tests: -- Unit: Verify directory structure exists -- Integration: Verify all dependencies installed -- Component: Verify placeholder files exist - -acceptance_criteria: -- All directories created -- All placeholder files exist -- All dependencies installed -- Project structure follows conventions - -validation: -- Run `ls -R src/` to verify structure -- Run `bun pm ls` to verify dependencies -- Check all placeholder files exist - -notes: -- Follow OpenTUI project structure conventions -- Keep structure organized and scalable -- Add comments to placeholder files -- Consider adding `eslint`, `prettier` for code quality diff --git a/tasks/podcast-tui-app/16-tab-navigation.md b/tasks/podcast-tui-app/16-tab-navigation.md deleted file mode 100644 index 0a58edc..0000000 --- a/tasks/podcast-tui-app/16-tab-navigation.md +++ /dev/null @@ -1,63 +0,0 @@ -# 15. Build Responsive Layout System (Flexbox) - -meta: - id: podcast-tui-app-15 - feature: podcast-tui-app - priority: P0 - depends_on: [14] - tags: [layout, flexbox, responsive, solidjs, opentui] - -objective: -- Create reusable Flexbox layout components -- Handle terminal resizing gracefully -- Ensure responsive design across different terminal sizes -- Build layout patterns for common UI patterns - -deliverables: -- `src/components/BoxLayout.tsx` with Flexbox container -- `src/components/Row.tsx` with horizontal layout -- `src/components/Column.tsx` with vertical layout -- `src/components/ResponsiveContainer.tsx` with resizing logic - -steps: -- Create `src/components/BoxLayout.tsx`: - - Generic Flexbox container - - Props: flexDirection, justifyContent, alignItems, gap - - Use OpenTUI layout patterns -- Create `src/components/Row.tsx`: - - Horizontal layout (flexDirection="row") - - Props for spacing and alignment - - Handle overflow -- Create `src/components/Column.tsx`: - - Vertical layout (flexDirection="column") - - Props for spacing and alignment - - Scrollable content area -- Create `src/components/ResponsiveContainer.tsx`: - - Listen to terminal resize events - - Adjust layout based on width/height - - Handle different screen sizes -- Add usage examples and documentation - -tests: -- Unit: Test BoxLayout with different props -- Unit: Test Row and Column layouts -- Integration: Test responsive behavior on resize -- Integration: Test layouts fit within terminal bounds - -acceptance_criteria: -- BoxLayout renders with correct Flexbox properties -- Row and Column layouts work correctly -- ResponsiveContainer adapts to terminal resize -- All layouts fit within terminal bounds - -validation: -- Run application and test layouts -- Resize terminal and verify responsive behavior -- Test with different terminal sizes -- Check layouts don't overflow - -notes: -- Use OpenTUI Flexbox patterns from `layout/REFERENCE.md` -- Terminal dimensions: width (columns) x height (rows) -- Handle edge cases (very small screens) -- Add comments explaining layout decisions diff --git a/tasks/podcast-tui-app/17-keyboard-handling.md b/tasks/podcast-tui-app/17-keyboard-handling.md deleted file mode 100644 index bd25f1a..0000000 --- a/tasks/podcast-tui-app/17-keyboard-handling.md +++ /dev/null @@ -1,60 +0,0 @@ -# 16. Implement Tab Navigation Component - -meta: - id: podcast-tui-app-16 - feature: podcast-tui-app - priority: P0 - depends_on: [15] - tags: [navigation, tabs, solidjs, opentui] - -objective: -- Create reusable tab navigation component -- Implement tab selection state -- Add keyboard navigation for tabs -- Handle active tab highlighting - -deliverables: -- `src/components/TabNavigation.tsx` with tab navigation -- `src/components/Tab.tsx` with individual tab component -- `src/hooks/useTabNavigation.ts` with tab logic - -steps: -- Create `src/components/TabNavigation.tsx`: - - Accept tabs array as prop - - Render tab buttons - - Manage selected tab state - - Update parent component on tab change -- Create `src/components/Tab.tsx`: - - Individual tab button - - Highlight selected tab - - Handle click and keyboard events - - Show tab icon if needed -- Create `src/hooks/useTabNavigation.ts`: - - Manage tab selection state - - Handle keyboard navigation (arrow keys) - - Update parent component - - Persist selected tab - -tests: -- Unit: Test Tab component renders correctly -- Unit: Test tab selection updates state -- Integration: Test keyboard navigation -- Integration: Test tab persists across renders - -acceptance_criteria: -- TabNavigation displays all tabs correctly -- Tab selection is visually indicated -- Keyboard navigation works (arrow keys, enter) -- Active tab persists - -validation: -- Run application and verify tabs appear -- Click tabs to test selection -- Use arrow keys to navigate -- Restart app and verify active tab persists - -notes: -- Use SolidJS `createSignal` for tab state -- Follow OpenTUI component patterns -- Tabs: Discover, My Feeds, Search, Player, Settings -- Add keyboard shortcuts documentation diff --git a/tasks/podcast-tui-app/18-sync-data-models.md b/tasks/podcast-tui-app/18-sync-data-models.md deleted file mode 100644 index 9ee9982..0000000 --- a/tasks/podcast-tui-app/18-sync-data-models.md +++ /dev/null @@ -1,69 +0,0 @@ -# 17. Add Keyboard Shortcuts and Navigation Handling - -meta: - id: podcast-tui-app-17 - feature: podcast-tui-app - priority: P1 - depends_on: [16] - tags: [keyboard, shortcuts, navigation, solidjs, opentui] - -objective: -- Implement global keyboard shortcuts -- Add shortcut documentation -- Handle keyboard events across components -- Prevent conflicts with input fields - -deliverables: -- `src/components/KeyboardHandler.tsx` with global shortcuts -- `src/components/ShortcutHelp.tsx` with shortcut list -- `src/hooks/useKeyboard.ts` with keyboard utilities -- `src/config/shortcuts.ts` with shortcut definitions - -steps: -- Create `src/config/shortcuts.ts`: - - Define all keyboard shortcuts - - Map keys to actions - - Document each shortcut -- Create `src/hooks/useKeyboard.ts`: - - Global keyboard event listener - - Filter events based on focused element - - Handle modifier keys (Ctrl, Shift, Alt) - - Prevent default browser behavior -- Create `src/components/KeyboardHandler.tsx`: - - Wrap application with keyboard handler - - Handle escape to close modals - - Handle Ctrl+Q to quit - - Handle Ctrl+S to save -- Create `src/components/ShortcutHelp.tsx`: - - Display all shortcuts - - Organize by category - - Click to copy shortcut - -tests: -- Unit: Test keyboard hook handles events -- Unit: Test modifier key combinations -- Integration: Test shortcuts work globally -- Integration: Test shortcuts don't interfere with inputs - -acceptance_criteria: -- All shortcuts work as defined -- Shortcuts help displays correctly -- Shortcuts don't interfere with input fields -- Escape closes modals - -validation: -- Run application and test each shortcut -- Try shortcuts in input fields (shouldn't trigger) -- Check ShortcutHelp displays all shortcuts -- Test Ctrl+Q quits app - -notes: -- Use OpenTUI keyboard patterns from `keyboard/REFERENCE.md` -- Common shortcuts: - - Ctrl+Q: Quit - - Ctrl+S: Save - - Escape: Close modal/exit input - - Arrow keys: Navigate - - Space: Play/Pause -- Document shortcuts in README -- Test on different terminals diff --git a/tasks/podcast-tui-app/19-import-export.md b/tasks/podcast-tui-app/19-import-export.md deleted file mode 100644 index a2a3d23..0000000 --- a/tasks/podcast-tui-app/19-import-export.md +++ /dev/null @@ -1,65 +0,0 @@ -# 18. Create Sync Data Models (JSON/XML Formats) - -meta: - id: podcast-tui-app-18 - feature: podcast-tui-app - priority: P1 - depends_on: [17] - tags: [data-models, json, xml, sync, typescript] - -objective: -- Define TypeScript interfaces for JSON sync format -- Define TypeScript interfaces for XML sync format -- Ensure compatibility between formats -- Add validation logic - -deliverables: -- `src/types/sync-json.ts` with JSON sync types -- `src/types/sync-xml.ts` with XML sync types -- `src/utils/sync-validation.ts` with validation logic -- `src/constants/sync-formats.ts` with format definitions - -steps: -- Create `src/types/sync-json.ts`: - - `SyncData` interface with all required fields - - Include feeds, sources, settings, preferences - - Add version field for format compatibility - - Add timestamp for last sync -- Create `src/types/sync-xml.ts`: - - `SyncDataXML` interface - - XML-compatible type definitions - - Root element and child elements - - Attributes for metadata -- Create `src/utils/sync-validation.ts`: - - `validateJSONSync(data: unknown): SyncData` - - `validateXMLSync(data: unknown): SyncDataXML` - - Field validation functions - - Type checking -- Create `src/constants/sync-formats.ts`: - - JSON format version - - XML format version - - Supported versions list - - Format extensions - -tests: -- Unit: Test JSON validation with valid/invalid data -- Unit: Test XML validation with valid/invalid data -- Integration: Test format compatibility - -acceptance_criteria: -- JSON sync types compile without errors -- XML sync types compile without errors -- Validation rejects invalid data -- Format versions are tracked - -validation: -- Run `bun run build` to verify TypeScript -- Test validation with sample data -- Test with invalid data to verify rejection - -notes: -- JSON format: Simple, human-readable -- XML format: More structured, better for complex data -- Include all necessary fields for complete sync -- Add comments explaining each field -- Ensure backward compatibility diff --git a/tasks/podcast-tui-app/20-file-picker.md b/tasks/podcast-tui-app/20-file-picker.md deleted file mode 100644 index 8bd46ed..0000000 --- a/tasks/podcast-tui-app/20-file-picker.md +++ /dev/null @@ -1,72 +0,0 @@ -# 19. Build Import/Export Functionality - -meta: - id: podcast-tui-app-19 - feature: podcast-tui-app - priority: P1 - depends_on: [18] - tags: [import-export, file-io, sync, solidjs] - -objective: -- Implement JSON export functionality -- Implement JSON import functionality -- Implement XML export functionality -- Implement XML import functionality -- Handle file operations and errors - -deliverables: -- `src/utils/sync.ts` with import/export functions -- `src/components/ExportDialog.tsx` with export UI -- `src/components/ImportDialog.tsx` with import UI -- Error handling components - -steps: -- Implement JSON export in `src/utils/sync.ts`: - - `exportFeedsToJSON(feeds: Feed[]): string` - - `exportSettingsToJSON(settings: Settings): string` - - Combine into `exportToJSON(data: SyncData): string` -- Implement JSON import in `src/utils/sync.ts`: - - `importFeedsFromJSON(json: string): Feed[]` - - `importSettingsFromJSON(json: string): Settings` - - Combine into `importFromJSON(json: string): SyncData` -- Implement XML export in `src/utils/sync.ts`: - - `exportToXML(data: SyncDataXML): string` - - XML serialization -- Implement XML import in `src/utils/sync.ts`: - - `importFromXML(xml: string): SyncDataXML` - - XML parsing -- Create `src/components/ExportDialog.tsx`: - - File name input - - Export format selection - - Export button - - Success message -- Create `src/components/ImportDialog.tsx`: - - File picker - - Format detection - - Import button - - Error message display - -tests: -- Unit: Test JSON import/export with sample data -- Unit: Test XML import/export with sample data -- Unit: Test error handling -- Integration: Test file operations - -acceptance_criteria: -- Export creates valid files -- Import loads data correctly -- Errors are handled gracefully -- Files can be opened in text editors - -validation: -- Run application and test export/import -- Open exported files in text editor -- Test with different data sizes -- Test error cases (invalid files) - -notes: -- Use `FileReader` API for file operations -- Handle file not found, invalid format, permission errors -- Add progress indicator for large files -- Support both JSON and XML formats -- Ensure data integrity during import diff --git a/tasks/podcast-tui-app/21-sync-status.md b/tasks/podcast-tui-app/21-sync-status.md deleted file mode 100644 index 90148c3..0000000 --- a/tasks/podcast-tui-app/21-sync-status.md +++ /dev/null @@ -1,61 +0,0 @@ -# 20. Create File Picker UI for Import - -meta: - id: podcast-tui-app-20 - feature: podcast-tui-app - priority: P1 - depends_on: [19] - tags: [file-picker, input, ui, solidjs, opentui] - -objective: -- Create file picker component for selecting import files -- Implement file format detection -- Display file information -- Handle file selection and validation - -deliverables: -- `src/components/FilePicker.tsx` with file picker UI -- `src/components/FileInfo.tsx` with file details -- `src/utils/file-detector.ts` with format detection - -steps: -- Create `src/utils/file-detector.ts`: - - `detectFormat(file: File): 'json' | 'xml' | 'unknown'` - - Read file header - - Check file extension - - Validate format -- Create `src/components/FilePicker.tsx`: - - File input using `` component - - Accept JSON and XML files - - File selection handler - - Clear button -- Create `src/components/FileInfo.tsx`: - - Display file name - - Display file size - - Display file format - - Display last modified date -- Add file picker to import dialog - -tests: -- Unit: Test format detection -- Unit: Test file picker selection -- Integration: Test file validation - -acceptance_criteria: -- File picker allows selecting files -- Format detection works correctly -- File information is displayed -- Invalid files are rejected - -validation: -- Run application and test file picker -- Select valid files -- Select invalid files -- Verify format detection - -notes: -- Use OpenTUI `` component for file picker -- Accept `.json` and `.xml` extensions -- Check file size limit (e.g., 10MB) -- Add file type validation -- Handle file selection errors diff --git a/tasks/podcast-tui-app/22-backup-restore.md b/tasks/podcast-tui-app/22-backup-restore.md deleted file mode 100644 index b784748..0000000 --- a/tasks/podcast-tui-app/22-backup-restore.md +++ /dev/null @@ -1,60 +0,0 @@ -# 21. Build Sync Status Indicator - -meta: - id: podcast-tui-app-21 - feature: podcast-tui-app - priority: P1 - depends_on: [20] - tags: [status-indicator, sync, ui, solidjs] - -objective: -- Create sync status indicator component -- Display sync state (idle, syncing, complete, error) -- Show sync progress -- Provide sync status in settings - -deliverables: -- `src/components/SyncStatus.tsx` with status indicator -- `src/components/SyncProgress.tsx` with progress bar -- `src/components/SyncError.tsx` with error display - -steps: -- Create `src/components/SyncStatus.tsx`: - - Display current sync state - - Show status icon (spinner, check, error) - - Show status message - - Auto-update based on state -- Create `src/components/SyncProgress.tsx`: - - Progress bar visualization - - Percentage display - - Step indicators - - Animation -- Create `src/components/SyncError.tsx`: - - Error message display - - Retry button - - Error details (expandable) -- Add sync status to settings screen - -tests: -- Unit: Test status indicator updates correctly -- Unit: Test progress bar visualization -- Unit: Test error display - -acceptance_criteria: -- Status indicator shows correct state -- Progress bar updates during sync -- Error message is displayed on errors -- Status persists across sync operations - -validation: -- Run application and test sync operations -- Trigger export/import -- Verify status indicator updates -- Test error cases - -notes: -- Use SolidJS signals for state -- Status states: idle, syncing, complete, error -- Use ASCII icons for status indicators -- Add animation for syncing state -- Make status component reusable diff --git a/tasks/podcast-tui-app/23-auth-state.md b/tasks/podcast-tui-app/23-auth-state.md deleted file mode 100644 index 4f3b3a4..0000000 --- a/tasks/podcast-tui-app/23-auth-state.md +++ /dev/null @@ -1,67 +0,0 @@ -# 22. Add Backup/Restore Functionality - -meta: - id: podcast-tui-app-22 - feature: podcast-tui-app - priority: P1 - depends_on: [21] - tags: [backup-restore, sync, data-protection, solidjs] - -objective: -- Implement backup functionality for all user data -- Implement restore functionality -- Create scheduled backups -- Add backup management UI - -deliverables: -- `src/utils/backup.ts` with backup functions -- `src/utils/restore.ts` with restore functions -- `src/components/BackupManager.tsx` with backup UI -- `src/components/ScheduledBackups.tsx` with backup settings - -steps: -- Create `src/utils/backup.ts`: - - `createBackup(): Promise` - - `backupFeeds(feeds: Feed[]): string` - - `backupSettings(settings: Settings): string` - - Include all user data -- Create `src/utils/restore.ts`: - - `restoreFromBackup(backupData: string): Promise` - - `restoreFeeds(backupData: string): void` - - `restoreSettings(backupData: string): void` - - Validate backup data -- Create `src/components/BackupManager.tsx`: - - List of backup files - - Restore button - - Delete backup button - - Create new backup button -- Create `src/components/ScheduledBackups.tsx`: - - Enable/disable scheduled backups - - Backup interval selection - - Last backup time display - - Manual backup button - -tests: -- Unit: Test backup creates valid files -- Unit: Test restore loads data correctly -- Unit: Test backup validation -- Integration: Test backup/restore workflow - -acceptance_criteria: -- Backup creates complete backup file -- Restore loads all data correctly -- Scheduled backups work as configured -- Backup files can be managed - -validation: -- Run application and create backup -- Restore from backup -- Test scheduled backups -- Verify data integrity - -notes: -- Backup file format: JSON with timestamp -- Include version info for compatibility -- Store backups in `backups/` directory -- Add backup encryption option (optional) -- Test with large data sets diff --git a/tasks/podcast-tui-app/24-login-screen.md b/tasks/podcast-tui-app/24-login-screen.md deleted file mode 100644 index 961f201..0000000 --- a/tasks/podcast-tui-app/24-login-screen.md +++ /dev/null @@ -1,61 +0,0 @@ -# 23. Create Authentication State (Disabled by Default) - -meta: - id: podcast-tui-app-23 - feature: podcast-tui-app - priority: P2 - depends_on: [22] - tags: [authentication, state, solidjs, security] - -objective: -- Create authentication state management -- Ensure authentication is disabled by default -- Set up user state structure -- Implement auth state persistence - -deliverables: -- `src/stores/auth.ts` with authentication store -- `src/types/auth.ts` with auth types -- `src/config/auth.ts` with auth configuration - -steps: -- Create `src/types/auth.ts`: - - `User` interface (id, email, name, createdAt) - - `AuthState` interface (user, isAuthenticated, isLoading) - - `AuthError` interface (code, message) -- Create `src/config/auth.ts`: - - `DEFAULT_AUTH_ENABLED = false` - - `AUTH_CONFIG` with settings - - `OAUTH_PROVIDERS` with provider info -- Create `src/stores/auth.ts`: - - `createAuthStore()` with Zustand - - `user` signal (initially null) - - `isAuthenticated` signal (initially false) - - `login()` function (placeholder) - - `logout()` function - - `validateCode()` function - - Persist state to localStorage -- Set up auth state in global store - -tests: -- Unit: Test auth state initializes correctly -- Unit: Test auth is disabled by default -- Unit: Test persistence - -acceptance_criteria: -- Authentication is disabled by default -- Auth store manages state correctly -- State persists across sessions -- Auth is optional and not required - -validation: -- Run application and verify auth is disabled -- Check localStorage for auth state -- Test login flow (should not work without backend) - -notes: -- Authentication is secondary to file sync -- No real backend, just UI/UX -- Focus on sync features -- User can choose to enable auth later -- Store auth state in localStorage diff --git a/tasks/podcast-tui-app/25-code-validation.md b/tasks/podcast-tui-app/25-code-validation.md deleted file mode 100644 index 2ba562a..0000000 --- a/tasks/podcast-tui-app/25-code-validation.md +++ /dev/null @@ -1,69 +0,0 @@ -# 24. Build Simple Login Screen (Email/Password) - -meta: - id: podcast-tui-app-24 - feature: podcast-tui-app - priority: P2 - depends_on: [23] - tags: [login, auth, form, solidjs, opentui] - -objective: -- Create login screen component -- Implement email input field -- Implement password input field -- Add login validation and error handling - -deliverables: -- `src/components/LoginScreen.tsx` with login form -- `src/components/EmailInput.tsx` with email field -- `src/components/PasswordInput.tsx` with password field -- `src/components/LoginButton.tsx` with submit button - -steps: -- Create `src/components/EmailInput.tsx`: - - Email input field using `` component - - Email validation regex - - Error message display - - Focus state styling -- Create `src/components/PasswordInput.tsx`: - - Password input field - - Show/hide password toggle - - Password validation rules - - Error message display -- Create `src/components/LoginButton.tsx`: - - Submit button - - Loading state - - Disabled state - - Error state -- Create `src/components/LoginScreen.tsx`: - - Combine inputs and button - - Login form validation - - Error handling - - Link to code validation - - Link to OAuth placeholder - -tests: -- Unit: Test email validation -- Unit: Test password validation -- Unit: Test login form submission -- Integration: Test login screen displays correctly - -acceptance_criteria: -- Login screen accepts email and password -- Validation works correctly -- Error messages display properly -- Form submission handled - -validation: -- Run application and navigate to login -- Enter valid credentials -- Enter invalid credentials -- Test error handling - -notes: -- Use OpenTUI `` component -- Email validation: regex pattern -- Password validation: minimum length -- No real authentication, just UI -- Link to code validation for sync -- Link to OAuth placeholder diff --git a/tasks/podcast-tui-app/26-oauth-placeholders.md b/tasks/podcast-tui-app/26-oauth-placeholders.md deleted file mode 100644 index bc70faa..0000000 --- a/tasks/podcast-tui-app/26-oauth-placeholders.md +++ /dev/null @@ -1,60 +0,0 @@ -# 25. Implement 8-Character Code Validation Flow - -meta: - id: podcast-tui-app-25 - feature: podcast-tui-app - priority: P2 - depends_on: [24] - tags: [code-validation, auth, sync, solidjs] - -objective: -- Create 8-character code input component -- Implement code validation logic -- Handle code submission -- Show validation results - -deliverables: -- `src/components/CodeInput.tsx` with code field -- `src/utils/code-validator.ts` with validation logic -- `src/components/CodeValidationResult.tsx` with result display - -steps: -- Create `src/utils/code-validator.ts`: - - `validateCode(code: string): boolean` - - Check length (8 characters) - - Check format (alphanumeric) - - Validate against stored codes -- Create `src/components/CodeInput.tsx`: - - 8-character code input - - Auto-formatting - - Clear button - - Validation error display -- Create `src/components/CodeValidationResult.tsx`: - - Success message - - Error message - - Retry button - - Link to alternative auth - -tests: -- Unit: Test code validation logic -- Unit: Test code input formatting -- Unit: Test validation result display - -acceptance_criteria: -- Code input accepts 8 characters -- Validation checks length and format -- Validation results display correctly -- Error handling works - -validation: -- Run application and test code validation -- Enter valid 8-character code -- Enter invalid code -- Test validation error display - -notes: -- Code format: alphanumeric (A-Z, 0-9) -- No real backend validation -- Store codes in localStorage for testing -- Link to OAuth placeholder -- Link to email/password login diff --git a/tasks/podcast-tui-app/27-sync-profile.md b/tasks/podcast-tui-app/27-sync-profile.md deleted file mode 100644 index feee0da..0000000 --- a/tasks/podcast-tui-app/27-sync-profile.md +++ /dev/null @@ -1,61 +0,0 @@ -# 26. Add OAuth Placeholder Screens (Document Limitations) - -meta: - id: podcast-tui-app-26 - feature: podcast-tui-app - priority: P2 - depends_on: [25] - tags: [oauth, documentation, placeholders, solidjs] - -objective: -- Create OAuth placeholder screens -- Document terminal limitations for OAuth -- Provide alternative authentication methods -- Explain browser redirect flow - -deliverables: -- `src/components/OAuthPlaceholder.tsx` with OAuth info -- `src/components/BrowserRedirect.tsx` with redirect flow -- `src/docs/oauth-limitations.md` with documentation - -steps: -- Create `src/components/OAuthPlaceholder.tsx`: - - Display OAuth information - - Explain terminal limitations - - Show supported providers (Google, Apple) - - Link to browser redirect flow -- Create `src/components/BrowserRedirect.tsx`: - - Display QR code for mobile - - Display 8-character code - - Instructions for browser flow - - Link to website -- Create `src/docs/oauth-limitations.md`: - - Detailed explanation of OAuth in terminal - - Why OAuth is not feasible - - Alternative authentication methods - - Browser redirect flow instructions -- Add OAuth placeholder to login screen - -tests: -- Unit: Test OAuth placeholder displays correctly -- Unit: Test browser redirect flow displays -- Documentation review - -acceptance_criteria: -- OAuth placeholder screens display correctly -- Limitations are clearly documented -- Alternative methods are provided -- Browser redirect flow is explained - -validation: -- Run application and navigate to OAuth placeholder -- Read documentation -- Verify flow instructions are clear - -notes: -- OAuth in terminal is not feasible -- Terminal cannot handle OAuth flows -- Document this limitation clearly -- Provide browser redirect as alternative -- User can still use file sync -- Google and Apple OAuth are supported by browser diff --git a/tasks/podcast-tui-app/28-feed-types.md b/tasks/podcast-tui-app/28-feed-types.md deleted file mode 100644 index 967c211..0000000 --- a/tasks/podcast-tui-app/28-feed-types.md +++ /dev/null @@ -1,62 +0,0 @@ -# 27. Create Sync-Only User Profile - -meta: - id: podcast-tui-app-27 - feature: podcast-tui-app - priority: P2 - depends_on: [26] - tags: [profile, sync, user-info, solidjs] - -objective: -- Create user profile component for sync-only users -- Display user information -- Show sync status -- Provide profile management options - -deliverables: -- `src/components/SyncProfile.tsx` with user profile -- `src/components/SyncStatus.tsx` with sync status -- `src/components/ProfileSettings.tsx` with profile settings - -steps: -- Create `src/components/SyncProfile.tsx`: - - User avatar/icon - - User name display - - Email display - - Sync status indicator - - Profile actions -- Create `src/components/SyncStatus.tsx`: - - Sync status (last sync time) - - Sync method (file-based) - - Sync frequency - - Sync history -- Create `src/components/ProfileSettings.tsx`: - - Edit profile - - Change password - - Manage sync settings - - Export data -- Add profile to settings screen - -tests: -- Unit: Test profile displays correctly -- Unit: Test sync status updates -- Integration: Test profile settings - -acceptance_criteria: -- Profile displays user information -- Sync status is shown -- Profile settings work correctly -- Profile is accessible from settings - -validation: -- Run application and navigate to profile -- View profile information -- Test profile settings -- Verify sync status - -notes: -- Profile for sync-only users -- No authentication required -- Profile data stored in localStorage -- Sync status shows last sync time -- Profile is optional diff --git a/tasks/podcast-tui-app/29-feed-list.md b/tasks/podcast-tui-app/29-feed-list.md deleted file mode 100644 index 0fe64b0..0000000 --- a/tasks/podcast-tui-app/29-feed-list.md +++ /dev/null @@ -1,56 +0,0 @@ -# 28. Create Feed Data Models and Types - -meta: - id: podcast-tui-app-28 - feature: podcast-tui-app - priority: P0 - depends_on: [27] - tags: [types, data-models, solidjs, typescript] - -objective: -- Define TypeScript interfaces for all podcast-related data -- Create models for feeds, episodes, sources -- Set up type definitions for sync functionality - -deliverables: -- `src/types/podcast.ts` with core types -- `src/types/episode.ts` with episode types -- `src/types/source.ts` with source types -- `src/types/feed.ts` with feed types - -steps: -- Create `src/types/podcast.ts`: - - `Podcast` interface (id, title, description, coverUrl, feedUrl, lastUpdated) - - `PodcastWithEpisodes` interface (podcast + episodes array) -- Create `src/types/episode.ts`: - - `Episode` interface (id, title, description, audioUrl, duration, pubDate, episodeNumber) - - `EpisodeStatus` enum (playing, paused, completed, not_started) - - `Progress` interface (episodeId, position, duration, timestamp) -- Create `src/types/source.ts`: - - `PodcastSource` interface (id, name, baseUrl, type, apiKey, enabled) - - `SourceType` enum (rss, api, custom) - - `SearchQuery` interface (query, sourceIds, filters) -- Create `src/types/feed.ts`: - - `Feed` interface (id, podcast, episodes[], isPublic, sourceId, lastUpdated) - - `FeedItem` interface (represents a single episode in a feed) - - `FeedFilter` interface (public, private, sourceId) - -tests: -- Unit: Verify all interfaces compile correctly -- Unit: Test enum values are correct -- Integration: Test type definitions match expected data structures - -acceptance_criteria: -- All TypeScript interfaces compile without errors -- Types are exported for use across the application -- Type definitions cover all podcast-related data - -validation: -- Run `bun run build` to verify TypeScript compilation -- Check `src/types/` directory contains all required files - -notes: -- Use strict TypeScript mode -- Include JSDoc comments for complex types -- Keep types simple and focused -- Ensure types are compatible with sync JSON/XML formats diff --git a/tasks/podcast-tui-app/30-source-management.md b/tasks/podcast-tui-app/30-source-management.md deleted file mode 100644 index bd988c8..0000000 --- a/tasks/podcast-tui-app/30-source-management.md +++ /dev/null @@ -1,63 +0,0 @@ -# 29. Build Feed List Component (Public/Private Feeds) - -meta: - id: podcast-tui-app-29 - feature: podcast-tui-app - priority: P0 - depends_on: [28] - tags: [feed-list, components, solidjs, opentui] - -objective: -- Create a scrollable feed list component -- Display public and private feeds -- Implement feed selection and display -- Add reverse chronological ordering - -deliverables: -- `src/components/FeedList.tsx` with feed list component -- `src/components/FeedItem.tsx` with individual feed item -- `src/components/FeedFilter.tsx` with public/private toggle - -steps: -- Create `src/components/FeedList.tsx`: - - Use `` for scrollable list - - Accept feeds array as prop - - Implement feed rendering with `createSignal` for selection - - Add keyboard navigation (arrow keys, enter) - - Display feed title, description, episode count -- Create `src/components/FeedItem.tsx`: - - Display feed information - - Show public/private indicator - - Highlight selected feed - - Add hover effects -- Create `src/components/FeedFilter.tsx`: - - Toggle button for public/private feeds - - Filter logic implementation - - Update parent FeedList when filtered -- Add feed list to "My Feeds" navigation tab - -tests: -- Unit: Test FeedList renders with feeds -- Unit: Test FeedItem displays correctly -- Integration: Test public/private filtering -- Integration: Test keyboard navigation in feed list - -acceptance_criteria: -- Feed list displays all feeds correctly -- Public/private toggle filters feeds -- Feed selection is visually indicated -- Keyboard navigation works (arrow keys, enter) -- List scrolls properly when many feeds - -validation: -- Run application and navigate to "My Feeds" -- Add some feeds and verify they appear -- Test public/private toggle -- Use arrow keys to navigate feeds -- Scroll list with many feeds - -notes: -- Use SolidJS `createSignal` for selection state -- Follow OpenTUI component patterns from `components/REFERENCE.md` -- Feed list should be scrollable with many items -- Use Flexbox for layout diff --git a/tasks/podcast-tui-app/31-reverse-chronological.md b/tasks/podcast-tui-app/31-reverse-chronological.md deleted file mode 100644 index 3d59943..0000000 --- a/tasks/podcast-tui-app/31-reverse-chronological.md +++ /dev/null @@ -1,67 +0,0 @@ -# 30. Implement Feed Source Management (Add/Remove Sources) - -meta: - id: podcast-tui-app-30 - feature: podcast-tui-app - priority: P1 - depends_on: [29] - tags: [source-management, feeds, solidjs, opentui] - -objective: -- Create source management component -- Implement add new source functionality -- Implement remove source functionality -- Manage enabled/disabled sources - -deliverables: -- `src/components/SourceManager.tsx` with source management UI -- `src/components/AddSourceForm.tsx` with add source form -- `src/components/SourceListItem.tsx` with individual source - -steps: -- Create `src/components/SourceManager.tsx`: - - List of enabled sources - - Add source button - - Remove source button - - Enable/disable toggle - - Source count display -- Create `src/components/AddSourceForm.tsx`: - - Source name input - - Source URL input - - Source type selection - - API key input (if required) - - Submit button - - Validation -- Create `src/components/SourceListItem.tsx`: - - Display source info - - Enable/disable toggle - - Remove button - - Status indicator -- Add source manager to settings screen - -tests: -- Unit: Test source list displays correctly -- Unit: Test add source form validation -- Unit: Test remove source functionality -- Integration: Test source management workflow - -acceptance_criteria: -- Source list displays all sources -- Add source form validates input -- Remove source works correctly -- Enable/disable toggles work - -validation: -- Run application and navigate to settings -- Test add source -- Test remove source -- Test enable/disable toggle -- Verify feeds from new sources appear - -notes: -- Source types: RSS, API, Custom -- RSS sources: feed URLs -- API sources: require API key -- Custom sources: user-defined -- Add validation for source URLs -- Store sources in localStorage diff --git a/tasks/podcast-tui-app/32-feed-detail.md b/tasks/podcast-tui-app/32-feed-detail.md deleted file mode 100644 index 0a5e8b8..0000000 --- a/tasks/podcast-tui-app/32-feed-detail.md +++ /dev/null @@ -1,61 +0,0 @@ -# 31. Add Reverse Chronological Ordering - -meta: - id: podcast-tui-app-31 - feature: podcast-tui-app - priority: P1 - depends_on: [30] - tags: [ordering, feeds, episodes, solidjs, sorting] - -objective: -- Implement reverse chronological ordering for feeds -- Order episodes by publication date (newest first) -- Order feeds by last updated (newest first) -- Provide sort option toggle - -deliverables: -- `src/utils/ordering.ts` with sorting functions -- `src/components/FeedSortToggle.tsx` with sort option -- `src/components/EpisodeList.tsx` with ordered episode list - -steps: -- Create `src/utils/ordering.ts`: - - `orderEpisodesByDate(episodes: Episode[]): Episode[]` - - Order by pubDate descending - - Handle missing dates - - `orderFeedsByDate(feeds: Feed[]): Feed[]` - - Order by lastUpdated descending - - Handle missing updates -- Create `src/components/FeedSortToggle.tsx`: - - Toggle button for date ordering - - Display current sort order - - Update parent component -- Create `src/components/EpisodeList.tsx`: - - Accept episodes array - - Apply ordering - - Display episodes in reverse chronological order - - Add keyboard navigation - -tests: -- Unit: Test episode ordering -- Unit: Test feed ordering -- Unit: Test sort toggle - -acceptance_criteria: -- Episodes ordered by date (newest first) -- Feeds ordered by last updated (newest first) -- Sort toggle works correctly -- Ordering persists across sessions - -validation: -- Run application and check episode order -- Check feed order -- Toggle sort order -- Restart app and verify ordering persists - -notes: -- Use JavaScript `sort()` with date comparison -- Handle timezone differences -- Add loading state during sort -- Cache ordered results -- Consider adding custom sort options diff --git a/tasks/podcast-tui-app/33-search-interface.md b/tasks/podcast-tui-app/33-search-interface.md deleted file mode 100644 index 2883c63..0000000 --- a/tasks/podcast-tui-app/33-search-interface.md +++ /dev/null @@ -1,66 +0,0 @@ -# 32. Create Feed Detail View - -meta: - id: podcast-tui-app-32 - feature: podcast-tui-app - priority: P1 - depends_on: [31] - tags: [feed-detail, episodes, solidjs, opentui] - -objective: -- Create feed detail view component -- Display podcast information -- List episodes in reverse chronological order -- Provide episode playback controls - -deliverables: -- `src/components/FeedDetail.tsx` with feed detail view -- `src/components/EpisodeList.tsx` with episode list -- `src/components/EpisodeItem.tsx` with individual episode - -steps: -- Create `src/components/FeedDetail.tsx`: - - Podcast cover image - - Podcast title and description - - Episode count - - Subscribe/unsubscribe button - - Episode list container -- Create `src/components/EpisodeList.tsx`: - - Scrollable episode list - - Display episode title, date, duration - - Playback status indicator - - Add keyboard navigation -- Create `src/components/EpisodeItem.tsx`: - - Episode information - - Play button - - Mark as complete button - - Progress bar - - Hover effects -- Add feed detail to "My Feeds" navigation tab - -tests: -- Unit: Test FeedDetail displays correctly -- Unit: Test EpisodeList rendering -- Unit: Test EpisodeItem interaction -- Integration: Test feed detail navigation - -acceptance_criteria: -- Feed detail displays podcast info -- Episode list shows all episodes -- Episodes ordered reverse chronological -- Play buttons work -- Mark as complete works - -validation: -- Run application and navigate to feed detail -- View podcast information -- Check episode order -- Test play button -- Test mark as complete - -notes: -- Use SolidJS `createSignal` for episode selection -- Display episode status (playing, completed, not started) -- Show progress for completed episodes -- Add episode filtering (all, completed, not completed) -- Use Flexbox for layout diff --git a/tasks/podcast-tui-app/34-multi-source-search.md b/tasks/podcast-tui-app/34-multi-source-search.md deleted file mode 100644 index 6b41283..0000000 --- a/tasks/podcast-tui-app/34-multi-source-search.md +++ /dev/null @@ -1,68 +0,0 @@ -# 33. Create Search Interface - -meta: - id: podcast-tui-app-33 - feature: podcast-tui-app - priority: P1 - depends_on: [32] - tags: [search-interface, input, solidjs, opentui] - -objective: -- Create search input component -- Implement search functionality -- Display search results -- Handle search state - -deliverables: -- `src/components/SearchBar.tsx` with search input -- `src/components/SearchResults.tsx` with results display -- `src/components/SearchHistory.tsx` with history list - -steps: -- Create `src/components/SearchBar.tsx`: - - Search input field using `` component - - Search button - - Clear button - - Enter key handler - - Loading state -- Create `src/utils/search.ts`: - - `searchPodcasts(query: string, sourceIds: string[]): Promise` - - `searchEpisodes(query: string, feedId: string): Promise` - - Handle multiple sources - - Cache search results -- Create `src/components/SearchResults.tsx`: - - Display search results with source indicators - - Show podcast/episode info - - Add to feed button - - Keyboard navigation through results -- Create `src/components/SearchHistory.tsx`: - - Display recent search queries - - Click to re-run search - - Delete individual history items - - Persist to localStorage - -tests: -- Unit: Test search logic returns correct results -- Unit: Test search history persistence -- Integration: Test search bar accepts input -- Integration: Test results display correctly - -acceptance_criteria: -- Search bar accepts and processes queries -- Search results display with source information -- Search history persists across sessions -- Keyboard navigation works in results - -validation: -- Run application and navigate to "Search" -- Type a query and press Enter -- Verify results appear -- Click a result to add to feed -- Restart app and verify history persists - -notes: -- Use localStorage for search history -- Implement basic caching to avoid repeated searches -- Handle empty results gracefully -- Add loading state during search -- Search both podcasts and episodes diff --git a/tasks/podcast-tui-app/35-search-results.md b/tasks/podcast-tui-app/35-search-results.md deleted file mode 100644 index c1c750f..0000000 --- a/tasks/podcast-tui-app/35-search-results.md +++ /dev/null @@ -1,62 +0,0 @@ -# 34. Implement Multi-Source Search - -meta: - id: podcast-tui-app-34 - feature: podcast-tui-app - priority: P1 - depends_on: [33] - tags: [multi-source, search, solidjs, api] - -objective: -- Implement search across multiple podcast sources -- Handle different source types (RSS, API, Custom) -- Display source information in results -- Cache search results - -deliverables: -- `src/utils/search.ts` with multi-source search logic -- `src/utils/source-searcher.ts` with source-specific searchers -- `src/components/SourceBadge.tsx` with source indicator - -steps: -- Create `src/utils/source-searcher.ts`: - - `searchRSSSource(query: string, source: PodcastSource): Promise` - - `searchAPISource(query: string, source: PodcastSource): Promise` - - `searchCustomSource(query: string, source: PodcastSource): Promise` - - Handle source-specific search logic -- Create `src/utils/search.ts`: - - `searchPodcasts(query: string, sourceIds: string[]): Promise` - - Aggregate results from multiple sources - - Deduplicate results - - Cache results by query - - Handle source errors gracefully -- Create `src/components/SourceBadge.tsx`: - - Display source type (RSS, API, Custom) - - Show source name - - Color-coded for different types - -tests: -- Unit: Test RSS source search -- Unit: Test API source search -- Unit: Test custom source search -- Unit: Test result aggregation - -acceptance_criteria: -- Search works across all enabled sources -- Source information displayed correctly -- Results aggregated from multiple sources -- Errors handled gracefully - -validation: -- Run application and perform search -- Verify results from multiple sources -- Test with different source types -- Test error handling for failed sources - -notes: -- RSS sources: Parse feed XML -- API sources: Call API endpoints -- Custom sources: User-defined search logic -- Handle rate limiting -- Cache results to avoid repeated searches -- Show loading state for each source diff --git a/tasks/podcast-tui-app/36-search-history.md b/tasks/podcast-tui-app/36-search-history.md deleted file mode 100644 index d9f94ad..0000000 --- a/tasks/podcast-tui-app/36-search-history.md +++ /dev/null @@ -1,66 +0,0 @@ -# 35. Add Search Results Display - -meta: - id: podcast-tui-app-35 - feature: podcast-tui-app - priority: P1 - depends_on: [34] - tags: [search-results, display, solidjs, opentui] - -objective: -- Display search results with rich information -- Show podcast/episode details -- Add to feed functionality -- Keyboard navigation through results - -deliverables: -- `src/components/SearchResults.tsx` with results display -- `src/components/ResultCard.tsx` with individual result -- `src/components/ResultDetail.tsx` with detailed view - -steps: -- Create `src/components/ResultCard.tsx`: - - Display result title - - Display source information - - Display description/snippet - - Add to feed button - - Click to view details -- Create `src/components/ResultDetail.tsx`: - - Full result details - - Podcast/episode information - - Episode list (if podcast) - - Subscribe button - - Close button -- Create `src/components/SearchResults.tsx`: - - Scrollable results list - - Empty state display - - Loading state display - - Error state display - - Keyboard navigation - -tests: -- Unit: Test ResultCard displays correctly -- Unit: Test ResultDetail displays correctly -- Unit: Test search results list -- Integration: Test add to feed - -acceptance_criteria: -- Search results display with all information -- Result cards show source and details -- Add to feed button works -- Keyboard navigation works - -validation: -- Run application and perform search -- Verify results display correctly -- Click result to view details -- Test add to feed -- Test keyboard navigation - -notes: -- Use SolidJS `createSignal` for result selection -- Display result type (podcast/episode) -- Show source name and type -- Add loading state during search -- Handle empty results -- Add pagination for large result sets diff --git a/tasks/podcast-tui-app/37-popular-shows.md b/tasks/podcast-tui-app/37-popular-shows.md deleted file mode 100644 index 2b98147..0000000 --- a/tasks/podcast-tui-app/37-popular-shows.md +++ /dev/null @@ -1,64 +0,0 @@ -# 36. Build Search History with Persistent Storage - -meta: - id: podcast-tui-app-36 - feature: podcast-tui-app - priority: P1 - depends_on: [35] - tags: [search-history, persistence, storage, solidjs] - -objective: -- Implement search history functionality -- Store search queries in localStorage -- Display recent searches -- Add search to history -- Clear history - -deliverables: -- `src/components/SearchHistory.tsx` with history list -- `src/utils/history.ts` with history management -- `src/hooks/useSearchHistory.ts` with history hook - -steps: -- Create `src/utils/history.ts`: - - `addToHistory(query: string): void` - - `getHistory(): string[]` - - `removeFromHistory(query: string): void` - - `clearHistory(): void` - - `maxHistorySize = 50` -- Create `src/hooks/useSearchHistory.ts`: - - `createSignal` for history array - - Update history on search - - Persist to localStorage - - Methods to manage history -- Create `src/components/SearchHistory.tsx`: - - Display recent search queries - - Click to re-run search - - Delete individual history items - - Clear all history button - - Persistent across sessions - -tests: -- Unit: Test history management functions -- Unit: Test history persistence -- Integration: Test history display - -acceptance_criteria: -- Search queries added to history -- History persists across sessions -- History displays correctly -- Clear history works - -validation: -- Run application and perform searches -- Check search history persists -- Test clearing history -- Restart app and verify - -notes: -- Use localStorage for persistence -- Limit history to 50 items -- Remove duplicates -- Store timestamps (optional) -- Clear history button in search screen -- Add delete button on individual items diff --git a/tasks/podcast-tui-app/38-discover-page.md b/tasks/podcast-tui-app/38-discover-page.md deleted file mode 100644 index 5a925fa..0000000 --- a/tasks/podcast-tui-app/38-discover-page.md +++ /dev/null @@ -1,56 +0,0 @@ -# 37. Create Popular Shows Data Structure - -meta: - id: podcast-tui-app-37 - feature: podcast-tui-app - priority: P1 - depends_on: [36] - tags: [popular-shows, data, discovery, solidjs] - -objective: -- Create data structure for popular shows -- Define podcast metadata -- Categorize shows by topic -- Include feed URLs and descriptions - -deliverables: -- `src/data/popular-shows.ts` with popular podcasts data -- `src/types/popular-shows.ts` with data types -- `src/constants/categories.ts` with category definitions - -steps: -- Create `src/types/popular-shows.ts`: - - `PopularPodcast` interface (id, title, description, coverUrl, feedUrl, category, tags) - - `Category` enum (Technology, Business, Science, Entertainment, Health, Education) -- Create `src/constants/categories.ts`: - - List of all categories - - Category descriptions - - Sample podcasts per category -- Create `src/data/popular-shows.ts`: - - Array of popular podcasts - - Categorized by topic - - Reverse chronological ordering - - Include metadata for each show - -tests: -- Unit: Test data structure compiles -- Unit: Test category definitions -- Integration: Test popular shows display - -acceptance_criteria: -- Popular shows data structure defined -- Categories defined correctly -- Shows categorized properly -- Data ready for display - -validation: -- Run `bun run build` to verify TypeScript -- Check data structure compiles -- Review data for completeness - -notes: -- Popular shows data can be static or fetched -- If sources don't provide trending, use curated list -- Categories help users find shows by topic -- Include diverse range of shows -- Add RSS feed URLs for easy subscription diff --git a/tasks/podcast-tui-app/39-trending-shows.md b/tasks/podcast-tui-app/39-trending-shows.md deleted file mode 100644 index ab0c21c..0000000 --- a/tasks/podcast-tui-app/39-trending-shows.md +++ /dev/null @@ -1,64 +0,0 @@ -# 38. Build Discover Page Component - -meta: - id: podcast-tui-app-38 - feature: podcast-tui-app - priority: P1 - depends_on: [37] - tags: [discover-page, popular-shows, solidjs, opentui] - -objective: -- Create discover page component -- Display popular shows -- Implement category filtering -- Add to feed functionality - -deliverables: -- `src/components/DiscoverPage.tsx` with discover UI -- `src/components/PopularShows.tsx` with shows grid -- `src/components/CategoryFilter.tsx` with category buttons - -steps: -- Create `src/components/DiscoverPage.tsx`: - - Page header with title - - Category filter buttons - - Popular shows grid - - Show details view - - Add to feed button -- Create `src/components/PopularShows.tsx`: - - Grid display of popular podcasts - - Show cover image - - Show title and description - - Add to feed button - - Click to view details -- Create `src/components/CategoryFilter.tsx`: - - Category button group - - Active category highlighting - - Filter logic implementation - - Update parent DiscoverPage - -tests: -- Unit: Test PopularShows displays correctly -- Unit: Test CategoryFilter functionality -- Integration: Test discover page navigation - -acceptance_criteria: -- Discover page displays popular shows -- Category filtering works correctly -- Shows are ordered reverse chronologically -- Clicking a show shows details -- Add to feed button works - -validation: -- Run application and navigate to "Discover" -- Verify popular shows appear -- Click different categories -- Click a show and verify details -- Try add to feed - -notes: -- Use Flexbox for category filter layout -- Use Grid for shows display -- Add loading state if fetching from API -- Handle empty categories -- Add hover effects for interactivity diff --git a/tasks/podcast-tui-app/40-category-filtering.md b/tasks/podcast-tui-app/40-category-filtering.md deleted file mode 100644 index 67fc3c1..0000000 --- a/tasks/podcast-tui-app/40-category-filtering.md +++ /dev/null @@ -1,61 +0,0 @@ -# 39. Add Trending Shows Display - -meta: - id: podcast-tui-app-39 - feature: podcast-tui-app - priority: P1 - depends_on: [38] - tags: [trending-shows, display, solidjs] - -objective: -- Display trending shows section -- Show top podcasts by popularity -- Implement trend indicators -- Display show rankings - -deliverables: -- `src/components/TrendingShows.tsx` with trending section -- `src/components/ShowRanking.tsx` with ranking display -- `src/components/TrendIndicator.tsx` with trend icon - -steps: -- Create `src/components/TrendingShows.tsx`: - - Trending section header - - Top shows list - - Show ranking (1, 2, 3...) - - Trending indicator - - Add to feed button -- Create `src/components/ShowRanking.tsx`: - - Display ranking number - - Show cover image - - Show title - - Trending score display -- Create `src/components/TrendIndicator.tsx`: - - Display trend icon (up arrow, down arrow, flat) - - Color-coded for trend direction - - Show trend percentage -- Add trending section to Discover page - -tests: -- Unit: Test TrendingShows displays correctly -- Unit: Test ranking display -- Unit: Test trend indicator - -acceptance_criteria: -- Trending shows section displays correctly -- Rankings shown for top shows -- Trend indicators display correctly -- Add to feed buttons work - -validation: -- Run application and navigate to "Discover" -- View trending shows section -- Check rankings and indicators -- Test add to feed - -notes: -- Trending shows: Top 10 podcasts -- Trending score: Based on downloads, listens, or engagement -- Trend indicators: Up/down/flat arrows -- Color-coded: Green for up, red for down, gray for flat -- Update trend scores periodically diff --git a/tasks/podcast-tui-app/42-playback-controls.md b/tasks/podcast-tui-app/42-playback-controls.md deleted file mode 100644 index 96040c6..0000000 --- a/tasks/podcast-tui-app/42-playback-controls.md +++ /dev/null @@ -1,62 +0,0 @@ -# 41. Create Player UI Layout - -meta: - id: podcast-tui-app-41 - feature: podcast-tui-app - priority: P0 - depends_on: [40] - tags: [player, layout, solidjs, opentui] - -objective: -- Create player UI layout component -- Display episode information -- Position player controls and waveform -- Handle player state - -deliverables: -- `src/components/Player.tsx` with player layout -- `src/components/PlayerHeader.tsx` with episode info -- `src/components/PlayerControls.tsx` with controls area - -steps: -- Create `src/components/Player.tsx`: - - Player container with borders - - Episode information header - - Waveform visualization area - - Playback controls area - - Progress bar area -- Create `src/components/PlayerHeader.tsx`: - - Episode title - - Podcast name - - Episode duration - - Close player button -- Create `src/components/PlayerControls.tsx`: - - Play/Pause button - - Previous/Next episode buttons - - Volume control - - Speed control - - Keyboard shortcuts display - -tests: -- Unit: Test Player layout renders correctly -- Unit: Test PlayerHeader displays correctly -- Unit: Test PlayerControls layout - -acceptance_criteria: -- Player UI displays episode information -- Controls positioned correctly -- Player fits within terminal bounds -- Layout is responsive - -validation: -- Run application and navigate to "Player" -- Select an episode to play -- Verify player UI displays -- Check layout and positioning - -notes: -- Use Flexbox for player layout -- Player should be at bottom or overlay -- Use `` for waveform area -- Add loading state when no episode -- Use SolidJS signals for player state diff --git a/tasks/podcast-tui-app/43-waveform-visualization.md b/tasks/podcast-tui-app/43-waveform-visualization.md deleted file mode 100644 index 9ec6fa8..0000000 --- a/tasks/podcast-tui-app/43-waveform-visualization.md +++ /dev/null @@ -1,70 +0,0 @@ -# 42. Implement Playback Controls - -meta: - id: podcast-tui-app-42 - feature: podcast-tui-app - priority: P0 - depends_on: [41] - tags: [playback, controls, audio, solidjs] - -objective: -- Implement playback controls (play/pause, skip, progress) -- Create control buttons -- Add keyboard shortcuts -- Handle playback state - -deliverables: -- `src/components/PlaybackControls.tsx` with control buttons -- `src/utils/playback.ts` with playback logic -- `src/hooks/usePlayback.ts` with playback hook - -steps: -- Create `src/utils/playback.ts`: - - `playAudio(audioUrl: string): void` - - `pauseAudio(): void` - - `skipForward(): void` - - `skipBackward(): void` - - `setPlaybackSpeed(speed: number): void` - - Handle audio player integration -- Create `src/hooks/usePlayback.ts`: - - `createSignal` for playback state - - Methods: play, pause, seek, setSpeed - - Handle audio events (timeupdate, ended) -- Create `src/components/PlaybackControls.tsx`: - - Play/Pause button - - Previous/Next episode buttons - - Volume control (slider) - - Speed control (1x, 1.25x, 1.5x, 2x) - - Keyboard shortcuts (space, arrows) - -tests: -- Unit: Test playback logic -- Unit: Test playback hook -- Integration: Test playback controls - -acceptance_criteria: -- Play/pause button works -- Skip forward/backward works -- Speed control works -- Volume control works -- Keyboard shortcuts work - -validation: -- Run application and test playback -- Press play/pause -- Test skip buttons -- Change playback speed -- Test keyboard shortcuts - -notes: -- Audio integration: Trigger system player or use Web Audio API -- Use Web Audio API for waveform -- Handle audio errors -- Add loading state during playback -- Store playback speed preference -- Support keyboard shortcuts: - - Space: Play/Pause - - Arrow Right: Skip forward - - Arrow Left: Skip backward - - Arrow Up: Volume up - - Arrow Down: Volume down diff --git a/tasks/podcast-tui-app/44-progress-tracking.md b/tasks/podcast-tui-app/44-progress-tracking.md deleted file mode 100644 index f2d60e0..0000000 --- a/tasks/podcast-tui-app/44-progress-tracking.md +++ /dev/null @@ -1,61 +0,0 @@ -# 43. Build ASCII Waveform Visualization - -meta: - id: podcast-tui-app-43 - feature: podcast-tui-app - priority: P0 - depends_on: [42] - tags: [waveform, visualization, ascii, solidjs] - -objective: -- Create ASCII waveform visualization -- Generate waveform from audio data -- Display waveform in player -- Handle click to seek - -deliverables: -- `src/components/Waveform.tsx` with waveform display -- `src/utils/waveform.ts` with waveform generation -- `src/components/WaveformProgress.tsx` with progress overlay - -steps: -- Create `src/utils/waveform.ts`: - - `generateWaveform(audioUrl: string): Promise` - - Fetch audio data - - Analyze audio frequencies - - Convert to ASCII characters - - Return ASCII waveform string -- Create `src/components/Waveform.tsx`: - - Display ASCII waveform - - Color-coded for played/paused - - Click to seek to position - - Handle terminal width -- Create `src/components/WaveformProgress.tsx`: - - Progress overlay on waveform - - Show current position - - Highlight current segment - -tests: -- Unit: Test waveform generation -- Unit: Test waveform display -- Integration: Test click to seek - -acceptance_criteria: -- Waveform displays correctly -- Waveform generated from audio -- Color-coded for played/paused -- Clicking waveform seeks to position - -validation: -- Run application and play an episode -- Verify waveform displays -- Check waveform colors -- Test seeking by clicking waveform - -notes: -- ASCII waveform: Use `#` for peaks, `.` for valleys -- Use Web Audio API for waveform generation -- Cache waveform data for performance -- Handle large audio files -- Use frame buffer for drawing -- Color scheme: Green for played, Gray for paused diff --git a/tasks/podcast-tui-app/45-audio-integration.md b/tasks/podcast-tui-app/45-audio-integration.md deleted file mode 100644 index 2eb46f1..0000000 --- a/tasks/podcast-tui-app/45-audio-integration.md +++ /dev/null @@ -1,63 +0,0 @@ -# 44. Add Progress Tracking and Seek - -meta: - id: podcast-tui-app-44 - feature: podcast-tui-app - priority: P0 - depends_on: [43] - tags: [progress, tracking, seek, solidjs] - -objective: -- Track playback progress -- Save progress to storage -- Implement seek functionality -- Display progress bar - -deliverables: -- `src/utils/progress.ts` with progress tracking -- `src/hooks/useProgress.ts` with progress hook -- `src/components/ProgressBar.tsx` with progress bar - -steps: -- Create `src/utils/progress.ts`: - - `saveProgress(episodeId: string, position: number, duration: number): void` - - `loadProgress(episodeId: string): Progress | null` - - `updateProgress(episodeId: string, position: number): void` - - Handle progress persistence -- Create `src/hooks/useProgress.ts`: - - `createSignal` for progress - - Update progress on timeupdate - - Save progress periodically - - Load progress on episode change -- Create `src/components/ProgressBar.tsx`: - - Progress bar visualization - - Percentage display - - Time display (current/total) - - Click to seek - -tests: -- Unit: Test progress tracking functions -- Unit: Test progress hook -- Integration: Test progress bar - -acceptance_criteria: -- Progress saved correctly -- Progress loaded correctly -- Seek works via progress bar -- Progress persists across sessions - -validation: -- Run application and play episode -- Seek to different positions -- Stop and restart -- Verify progress saved -- Restart app and verify progress loaded - -notes: -- Save progress to localStorage -- Auto-save every 30 seconds -- Save on episode change -- Save on pause -- Don't save on completion -- Load progress when starting episode -- Display progress bar in player diff --git a/tasks/podcast-tui-app/46-settings-screen.md b/tasks/podcast-tui-app/46-settings-screen.md deleted file mode 100644 index 0f18261..0000000 --- a/tasks/podcast-tui-app/46-settings-screen.md +++ /dev/null @@ -1,64 +0,0 @@ -# 45. Implement Audio Integration (System/External Player) - -meta: - id: podcast-tui-app-45 - feature: podcast-tui-app - priority: P0 - depends_on: [44] - tags: [audio, integration, player, solidjs] - -objective: -- Implement audio playback integration -- Support system audio player -- Support external player -- Handle audio events - -deliverables: -- `src/utils/audio-player.ts` with audio integration -- `src/components/AudioSettings.tsx` with audio player settings -- `src/hooks/useAudio.ts` with audio hook - -steps: -- Create `src/utils/audio-player.ts`: - - `playWithSystemPlayer(audioUrl: string): void` - - `playWithExternalPlayer(audioUrl: string): void` - - `getSupportedPlayers(): string[]` - - Detect available players -- Create `src/components/AudioSettings.tsx`: - - Player selection dropdown - - System player options - - External player options - - Default player selection -- Create `src/hooks/useAudio.ts`: - - Manage audio state - - Handle player selection - - Handle audio events - - Update progress - -tests: -- Unit: Test audio player detection -- Unit: Test player selection -- Integration: Test audio playback - -acceptance_criteria: -- Audio plays correctly -- Multiple player options available -- Player selection persists -- Audio events handled - -validation: -- Run application and test audio -- Select different player options -- Play episode and verify audio -- Test progress tracking with audio - -notes: -- System player: macOS Terminal, iTerm2, etc. -- External player: VLC, QuickTime, etc. -- Use `open` command for macOS -- Use `xdg-open` for Linux -- Use `start` for Windows -- Handle audio errors gracefully -- Add player selection in settings -- Default to system player -- Store player preference diff --git a/tasks/podcast-tui-app/47-source-management-ui.md b/tasks/podcast-tui-app/47-source-management-ui.md deleted file mode 100644 index 9fa9425..0000000 --- a/tasks/podcast-tui-app/47-source-management-ui.md +++ /dev/null @@ -1,66 +0,0 @@ -# 46. Create Settings Screen - -meta: - id: podcast-tui-app-46 - feature: podcast-tui-app - priority: P1 - depends_on: [45] - tags: [settings, screen, solidjs, opentui] - -objective: -- Create settings screen component -- Implement settings navigation -- Display all settings sections -- Save/apply settings - -deliverables: -- `src/components/SettingsScreen.tsx` with settings UI -- `src/components/SettingsNavigation.tsx` with settings menu -- `src/components/SettingsContent.tsx` with settings content - -steps: -- Create `src/components/SettingsNavigation.tsx`: - - Settings menu with sections - - Navigation between sections - - Active section highlighting - - Keyboard navigation -- Create `src/components/SettingsContent.tsx`: - - Feed settings section - - Player settings section - - Sync settings section - - Appearance settings section - - Account settings section -- Create `src/components/SettingsScreen.tsx`: - - Combine navigation and content - - Save/Cancel buttons - - Reset to defaults button - -tests: -- Unit: Test SettingsNavigation displays correctly -- Unit: Test SettingsContent sections -- Integration: Test settings navigation - -acceptance_criteria: -- Settings screen displays all sections -- Navigation between sections works -- Settings save correctly -- Settings persist - -validation: -- Run application and navigate to "Settings" -- Navigate between settings sections -- Change settings -- Verify settings persist -- Test reset to defaults - -notes: -- Use SolidJS signals for settings state -- Settings sections: - - Feeds: Source management, auto-download - - Player: Playback speed, auto-play next - - Sync: Backup frequency, sync method - - Appearance: Theme, font size - - Account: Login, sync profile -- Add keyboard shortcuts for navigation -- Save settings on change -- Reset to defaults button diff --git a/tasks/podcast-tui-app/48-user-preferences.md b/tasks/podcast-tui-app/48-user-preferences.md deleted file mode 100644 index 8fb5430..0000000 --- a/tasks/podcast-tui-app/48-user-preferences.md +++ /dev/null @@ -1,69 +0,0 @@ -# 47. Add Source Management UI - -meta: - id: podcast-tui-app-47 - feature: podcast-tui-app - priority: P1 - depends_on: [46] - tags: [source-management, settings, ui, solidjs] - -objective: -- Create source management UI in settings -- List all enabled sources -- Add new source functionality -- Remove source functionality -- Enable/disable sources - -deliverables: -- `src/components/SourceManager.tsx` with source management -- `src/components/AddSourceForm.tsx` with add source form -- `src/components/SourceListItem.tsx` with individual source - -steps: -- Create `src/components/SourceManager.tsx`: - - List of enabled sources - - Add source button - - Remove source button - - Enable/disable toggle - - Source count display -- Create `src/components/AddSourceForm.tsx`: - - Source name input - - Source URL input - - Source type selection (RSS, API, Custom) - - API key input (if required) - - Submit button - - Validation -- Create `src/components/SourceListItem.tsx`: - - Display source info - - Enable/disable toggle - - Remove button - - Status indicator (working, error) - -tests: -- Unit: Test source list displays correctly -- Unit: Test add source form validation -- Unit: Test remove source functionality -- Integration: Test source management workflow - -acceptance_criteria: -- Source list displays all sources -- Add source form validates input -- Remove source works correctly -- Enable/disable toggles work - -validation: -- Run application and navigate to settings -- Test add source -- Test remove source -- Test enable/disable toggle -- Verify feeds from new sources appear - -notes: -- Source types: RSS, API, Custom -- RSS sources: feed URLs -- API sources: require API key -- Custom sources: user-defined -- Add validation for source URLs -- Store sources in localStorage -- Show source status (working/error) -- Add error handling for failed sources diff --git a/tasks/podcast-tui-app/49-data-persistence.md b/tasks/podcast-tui-app/49-data-persistence.md deleted file mode 100644 index 832ffbb..0000000 --- a/tasks/podcast-tui-app/49-data-persistence.md +++ /dev/null @@ -1,69 +0,0 @@ -# 48. Build User Preferences - -meta: - id: podcast-tui-app-48 - feature: podcast-tui-app - priority: P1 - depends_on: [47] - tags: [preferences, settings, solidjs] - -objective: -- Create user preferences panel -- Implement theme selection -- Implement font size control -- Implement playback speed control -- Implement auto-download settings - -deliverables: -- `src/components/PreferencesPanel.tsx` with preferences -- `src/components/ThemeSelector.tsx` with theme options -- `src/components/FontSelector.tsx` with font size options -- `src/components/AutoDownload.tsx` with auto-download settings - -steps: -- Create `src/components/PreferencesPanel.tsx`: - - Preferences sections - - Navigation between preferences - - Save/Cancel buttons -- Create `src/components/ThemeSelector.tsx`: - - Theme options (light, dark, terminal) - - Preview theme - - Select theme -- Create `src/components/FontSelector.tsx`: - - Font size options (small, medium, large) - - Preview font size - - Select font size -- Create `src/components/AutoDownload.tsx`: - - Auto-download episodes - - Download after playback - - Download schedule - - Storage limit warning - -tests: -- Unit: Test theme selector -- Unit: Test font selector -- Unit: Test auto-download settings -- Integration: Test preferences save/load - -acceptance_criteria: -- Preferences display correctly -- Theme selection works -- Font size selection works -- Auto-download settings work -- Preferences persist - -validation: -- Run application and navigate to preferences -- Change theme and verify -- Change font size and verify -- Test auto-download settings -- Restart app and verify preferences - -notes: -- Use localStorage for preferences -- Theme: Terminal colors (green/amber on black) -- Font size: Small (12px), Medium (14px), Large (16px) -- Auto-download: Download completed episodes -- Add preferences to settings screen -- Save preferences on change -- Reset to defaults button diff --git a/tasks/podcast-tui-app/50-global-state-store.md b/tasks/podcast-tui-app/50-global-state-store.md deleted file mode 100644 index 59e8b67..0000000 --- a/tasks/podcast-tui-app/50-global-state-store.md +++ /dev/null @@ -1,52 +0,0 @@ -# 50. Create Global State Store (Signals) - -meta: - id: podcast-tui-app-50 - feature: podcast-tui-app - priority: P1 - depends_on: [66] - tags: [state-management, signals, solidjs, global-store] - -objective: -- Create a global state store using SolidJS Signals for reactive state management -- Implement a store that manages application-wide state (feeds, settings, user, etc.) -- Provide reactive subscriptions for state changes -- Ensure thread-safe state updates - -deliverables: -- `/src/store/index.ts` - Global state store with Signals -- `/src/store/types.ts` - State type definitions -- `/src/store/hooks.ts` - Custom hooks for state access -- Updated `src/index.tsx` to initialize the store - -steps: -- Define state interface with all application state properties (feeds, settings, user, etc.) -- Create Signal-based store using `createSignal` from SolidJS -- Implement computed signals for derived state (filtered feeds, search results, etc.) -- Create state update functions that trigger reactivity -- Add subscription mechanism for reactive UI updates -- Export store and hooks for use across components - -tests: -- Unit: Test that signals update correctly when state changes -- Unit: Test computed signals produce correct derived values -- Integration: Verify store updates trigger UI re-renders -- Integration: Test multiple components can subscribe to same state - -acceptance_criteria: -- Store can be initialized with default state -- State changes trigger reactive updates in components -- Computed signals work correctly for derived state -- Multiple components can access and subscribe to store -- State updates are thread-safe - -validation: -- Run `bun run build` to verify TypeScript compilation -- Run application and verify state changes are reactive -- Check console for any errors during state updates - -notes: -- Use `createSignal`, `createComputed`, `createEffect` from SolidJS -- Store should follow single source of truth pattern -- Consider using `batch` for multiple state updates -- State should be serializable for persistence diff --git a/tasks/podcast-tui-app/51-api-client.md b/tasks/podcast-tui-app/51-api-client.md deleted file mode 100644 index 9190538..0000000 --- a/tasks/podcast-tui-app/51-api-client.md +++ /dev/null @@ -1,62 +0,0 @@ -# 50. Create Global State Store (Signals) - -meta: - id: podcast-tui-app-50 - feature: podcast-tui-app - priority: P0 - depends_on: [49] - tags: [state-management, global-store, signals, solidjs] - -objective: -- Create global state store using SolidJS Signals -- Manage application-wide state -- Provide state to all components -- Handle state updates and persistence - -deliverables: -- `src/stores/appStore.ts` with global state store -- `src/stores/feedStore.ts` with feed state -- `src/stores/playerStore.ts` with player state -- `src/stores/searchStore.ts` with search state - -steps: -- Create `src/stores/appStore.ts`: - - Use SolidJS signals for global state - - Store application state: currentTab, isAuthEnabled, settings - - Provide state to all child components - - Update state when needed -- Create `src/stores/feedStore.ts`: - - Signals for feeds array - - Signals for selectedFeed - - Methods: addFeed, removeFeed, updateFeed -- Create `src/stores/playerStore.ts`: - - Signals for currentEpisode - - Signals for playbackState - - Methods: play, pause, seek, setSpeed -- Create `src/stores/searchStore.ts`: - - Signals for searchResults - - Signals for searchHistory - - Methods: search, addToHistory, clearHistory - -tests: -- Unit: Test store methods update signals correctly -- Unit: Test state persistence -- Integration: Test state updates across components - -acceptance_criteria: -- Global state store manages all app state -- Store methods update signals correctly -- State persists across component re-renders -- State updates propagate to UI - -validation: -- Run application and verify state is initialized -- Modify state and verify UI updates -- Restart app and verify state persistence - -notes: -- Use SolidJS `createSignal` for reactivity -- Store should be singleton pattern -- Use `createStore` if complex state needed -- Keep store simple and focused -- Store state in localStorage diff --git a/tasks/podcast-tui-app/52-data-fetching-caching.md b/tasks/podcast-tui-app/52-data-fetching-caching.md deleted file mode 100644 index d82a2b8..0000000 --- a/tasks/podcast-tui-app/52-data-fetching-caching.md +++ /dev/null @@ -1,63 +0,0 @@ -# 51. Implement API Client for Podcast Sources - -meta: - id: podcast-tui-app-51 - feature: podcast-tui-app - priority: P1 - depends_on: [50] - tags: [api-client, sources, solidjs] - -objective: -- Create API client for podcast sources -- Handle RSS feed parsing -- Handle API source queries -- Handle custom sources -- Implement error handling - -deliverables: -- `src/api/client.ts` with API client -- `src/api/rss-parser.ts` with RSS parsing -- `src/api/source-handler.ts` with source-specific handlers - -steps: -- Create `src/api/client.ts`: - - `fetchFeeds(sourceIds: string[]): Promise` - - `fetchEpisodes(feedUrl: string): Promise` - - `searchPodcasts(query: string): Promise` - - Handle API calls and errors -- Create `src/api/rss-parser.ts`: - - `parseRSSFeed(xml: string): Podcast` - - Parse RSS XML - - Extract podcast metadata - - Extract episodes -- Create `src/api/source-handler.ts`: - - `handleRSSSource(source: PodcastSource): Promise` - - `handleAPISource(source: PodcastSource, query: string): Promise` - - `handleCustomSource(source: PodcastSource, query: string): Promise` - - Source-specific logic - -tests: -- Unit: Test RSS parsing -- Unit: Test API client -- Unit: Test source handlers -- Integration: Test API calls - -acceptance_criteria: -- API client fetches data correctly -- RSS parsing works -- Source handlers work for all types -- Errors handled gracefully - -validation: -- Run application and test API calls -- Test RSS feed parsing -- Test API source queries -- Test error handling - -notes: -- Use `feed-parser` library for RSS parsing -- Use `axios` for API calls -- Handle rate limiting -- Cache API responses -- Add error handling for failed requests -- Store API keys securely diff --git a/tasks/podcast-tui-app/53-file-based-storage.md b/tasks/podcast-tui-app/53-file-based-storage.md deleted file mode 100644 index d085015..0000000 --- a/tasks/podcast-tui-app/53-file-based-storage.md +++ /dev/null @@ -1,66 +0,0 @@ -# 52. Add Data Fetching and Caching - -meta: - id: podcast-tui-app-52 - feature: podcast-tui-app - priority: P1 - depends_on: [51] - tags: [data-fetching, caching, performance, solidjs] - -objective: -- Implement data fetching with caching -- Cache podcast feeds and episodes -- Cache search results -- Cache popular shows -- Handle cache invalidation - -deliverables: -- `src/utils/cache.ts` with cache management -- `src/utils/data-fetcher.ts` with data fetching logic -- `src/hooks/useCachedData.ts` with cache hook - -steps: -- Create `src/utils/cache.ts`: - - `cacheFeed(feedUrl: string, data: Feed): void` - - `getCachedFeed(feedUrl: string): Feed | null` - - `cacheSearch(query: string, results: Podcast[]): void` - - `getCachedSearch(query: string): Podcast[] | null` - - `invalidateCache(type: string): void` - - `cacheExpiration = 3600000` (1 hour) -- Create `src/utils/data-fetcher.ts`: - - `fetchFeedWithCache(feedUrl: string): Promise` - - `fetchEpisodesWithCache(feedUrl: string): Promise` - - `searchWithCache(query: string): Promise` - - Use cache when available -- Create `src/hooks/useCachedData.ts`: - - `createSignal` for cached data - - Fetch data with cache - - Update cache on fetch - - Handle cache expiration - -tests: -- Unit: Test cache management -- Unit: Test data fetcher -- Unit: Test cache hook - -acceptance_criteria: -- Data is cached correctly -- Cache is used on subsequent requests -- Cache invalidation works -- Cache expiration handled - -validation: -- Run application and fetch data -- Verify data is cached -- Make same request and use cache -- Test cache invalidation -- Test cache expiration - -notes: -- Use localStorage for cache -- Cache feeds, episodes, search results -- Cache popular shows -- Invalidate cache on feed update -- Set cache expiration time -- Add cache size limit -- Clear cache on settings change diff --git a/tasks/podcast-tui-app/54-testing-framework.md b/tasks/podcast-tui-app/54-testing-framework.md deleted file mode 100644 index fcf4f37..0000000 --- a/tasks/podcast-tui-app/54-testing-framework.md +++ /dev/null @@ -1,65 +0,0 @@ -# 53. Build File-Based Storage for Sync - -meta: - id: podcast-tui-app-53 - feature: podcast-tui-app - priority: P1 - depends_on: [52] - tags: [file-based-storage, sync, solidjs] - -objective: -- Implement file-based storage for sync -- Store feeds and settings in files -- Implement backup functionality -- Implement restore functionality -- Handle file operations - -deliverables: -- `src/utils/file-storage.ts` with file storage -- `src/utils/backup.ts` with backup functions -- `src/utils/restore.ts` with restore functions - -steps: -- Create `src/utils/file-storage.ts`: - - `saveFeedsToFile(feeds: Feed[]): Promise` - - `loadFeedsFromFile(): Promise` - - `saveSettingsToFile(settings: Settings): Promise` - - `loadSettingsFromFile(): Promise` - - Handle file operations and errors -- Create `src/utils/backup.ts`: - - `createBackup(): Promise` - - `backupFeeds(feeds: Feed[]): string` - - `backupSettings(settings: Settings): string` - - Include all user data - - Create backup directory -- Create `src/utils/restore.ts`: - - `restoreFromBackup(backupData: string): Promise` - - `restoreFeeds(backupData: string): void` - - `restoreSettings(backupData: string): void` - - Validate backup data - -tests: -- Unit: Test file storage functions -- Unit: Test backup functions -- Unit: Test restore functions - -acceptance_criteria: -- File storage works correctly -- Backup creates valid files -- Restore loads data correctly -- File operations handle errors - -validation: -- Run application and test file storage -- Create backup -- Restore from backup -- Test with different data sizes -- Test error cases - -notes: -- Store files in `data/` directory -- Use JSON format for storage -- Include version info for compatibility -- Add file encryption option (optional) -- Test with large files -- Handle file permission errors diff --git a/tasks/podcast-tui-app/55-component-tests.md b/tasks/podcast-tui-app/55-component-tests.md deleted file mode 100644 index cfd3d86..0000000 --- a/tasks/podcast-tui-app/55-component-tests.md +++ /dev/null @@ -1,67 +0,0 @@ -# 54. Set Up Testing Framework (Snapshot Testing) - -meta: - id: podcast-tui-app-54 - feature: podcast-tui-app - priority: P1 - depends_on: [53] - tags: [testing, framework, setup, opentui] - -objective: -- Set up OpenTUI testing framework -- Configure test runner -- Set up snapshot testing -- Configure test environment - -deliverables: -- `tests/` directory with test structure -- `tests/setup.ts` with test setup -- `tests/config.ts` with test configuration -- `package.json` with test scripts - -steps: -- Install testing dependencies: - - `@opentui/testing` - - `@opentui/react` (for testing) - - `@opentui/solid` (for testing) - - `bun test` (built-in test runner) -- Configure test environment: - - Set up test renderer - - Configure test options - - Set up test fixtures -- Create test structure: - - `tests/components/` for component tests - - `tests/integration/` for integration tests - - `tests/utils/` for utility tests - - `tests/fixtures/` for test data -- Add test scripts to `package.json`: - - `test`: Run all tests - - `test:watch`: Run tests in watch mode - - `test:coverage`: Run tests with coverage - - `test:components`: Run component tests - - `test:integration`: Run integration tests - -tests: -- Unit: Verify testing framework is set up -- Integration: Verify test scripts work -- Component: Verify snapshot testing works - -acceptance_criteria: -- Testing framework installed and configured -- Test scripts work correctly -- Snapshot testing configured -- Test environment set up - -validation: -- Run `bun test` to execute all tests -- Run `bun test:components` to run component tests -- Run `bun test:coverage` for coverage report -- Check all tests pass - -notes: -- Use OpenTUI's testing framework for snapshot testing -- Test components in isolation -- Use test fixtures for common data -- Mock external dependencies -- Keep tests fast and focused -- Add CI/CD integration for automated testing diff --git a/tasks/podcast-tui-app/56-keyboard-tests.md b/tasks/podcast-tui-app/56-keyboard-tests.md deleted file mode 100644 index 4cff05b..0000000 --- a/tasks/podcast-tui-app/56-keyboard-tests.md +++ /dev/null @@ -1,68 +0,0 @@ -# 55. Write Component Tests - -meta: - id: podcast-tui-app-55 - feature: podcast-tui-app - priority: P1 - depends_on: [54] - tags: [testing, components, snapshot, solidjs] - -objective: -- Write component tests for all major components -- Test component rendering -- Test component behavior -- Test component interactions - -deliverables: -- `tests/components/Navigation.test.tsx` -- `tests/components/FeedList.test.tsx` -- `tests/components/SearchBar.test.tsx` -- `tests/components/Player.test.tsx` -- `tests/components/SettingsScreen.test.tsx` -- Test coverage for all components - -steps: -- Write component tests: - - `tests/components/Navigation.test.tsx`: - - Test Navigation renders with correct tabs - - Test tab selection updates state - - Test keyboard navigation - - `tests/components/FeedList.test.tsx`: - - Test FeedList renders with feeds - - Test FeedItem displays correctly - - Test public/private filtering - - Test keyboard navigation - - `tests/components/SearchBar.test.tsx`: - - Test search bar accepts input - - Test search button triggers search - - Test clear button clears input - - `tests/components/Player.test.tsx`: - - Test Player UI displays correctly - - Test playback controls work - - Test waveform visualization - - `tests/components/SettingsScreen.test.tsx`: - - Test SettingsScreen renders correctly - - Test settings navigation - - Test settings save/load - -tests: -- Unit: Run all component tests -- Coverage: Verify component coverage - -acceptance_criteria: -- All component tests pass -- Test coverage > 80% -- Component behavior verified - -validation: -- Run `bun test:components` to execute component tests -- Run `bun test --coverage` for coverage report -- Fix any failing tests - -notes: -- Use OpenTUI testing framework -- Test components in isolation -- Mock external dependencies -- Use test fixtures for data -- Keep tests fast -- Test both happy path and error cases diff --git a/tasks/podcast-tui-app/57-error-handling.md b/tasks/podcast-tui-app/57-error-handling.md deleted file mode 100644 index 27667d2..0000000 --- a/tasks/podcast-tui-app/57-error-handling.md +++ /dev/null @@ -1,57 +0,0 @@ -# 56. Add Keyboard Interaction Tests - -meta: - id: podcast-tui-app-56 - feature: podcast-tui-app - priority: P1 - depends_on: [55] - tags: [testing, keyboard, interaction, solidjs] - -objective: -- Write keyboard interaction tests -- Test keyboard shortcuts -- Test keyboard navigation -- Test input field keyboard handling - -deliverables: -- `tests/integration/navigation.test.tsx` -- `tests/integration/keyboard.test.tsx` -- `tests/integration/inputs.test.tsx` - -steps: -- Write keyboard tests: - - `tests/integration/navigation.test.tsx`: - - Test arrow keys navigate tabs - - Test enter selects tab - - Test escape closes modal - - `tests/integration/keyboard.test.tsx`: - - Test Ctrl+Q quits app - - Test Ctrl+S saves settings - - Test space plays/pauses - - Test arrow keys for playback - - `tests/integration/inputs.test.tsx`: - - Test input field accepts text - - Test keyboard input in search bar - - Test code input validation - -tests: -- Unit: Run keyboard tests -- Integration: Test keyboard interactions - -acceptance_criteria: -- All keyboard tests pass -- Keyboard shortcuts work correctly -- Input fields handle keyboard input - -validation: -- Run `bun test` to execute all tests -- Manually test keyboard shortcuts -- Verify keyboard navigation works - -notes: -- Use OpenTUI testing framework -- Test keyboard events -- Mock keyboard input -- Test on different terminals -- Document keyboard shortcuts -- Test modifier key combinations diff --git a/tasks/podcast-tui-app/58-loading-states.md b/tasks/podcast-tui-app/58-loading-states.md deleted file mode 100644 index fa956a8..0000000 --- a/tasks/podcast-tui-app/58-loading-states.md +++ /dev/null @@ -1,63 +0,0 @@ -# 57. Implement Error Handling - -meta: - id: podcast-tui-app-57 - feature: podcast-tui-app - priority: P1 - depends_on: [56] - tags: [error-handling, debugging, solidjs] - -objective: -- Implement error handling throughout the app -- Handle API errors -- Handle file errors -- Handle user input errors -- Display error messages - -deliverables: -- `src/utils/error-handler.ts` with error handling -- `src/components/ErrorDisplay.tsx` with error UI -- `src/components/LoadingSpinner.tsx` with loading UI - -steps: -- Create `src/utils/error-handler.ts`: - - `handleError(error: Error, context: string): void` - - Log errors to console - - Display user-friendly error messages - - Handle different error types -- Create `src/components/ErrorDisplay.tsx`: - - Display error message - - Error type indicator - - Retry button - - Error details (expandable) -- Create `src/components/LoadingSpinner.tsx`: - - Loading indicator - - Loading message - - Different loading states - -tests: -- Unit: Test error handler -- Unit: Test error display -- Integration: Test error handling flow - -acceptance_criteria: -- Errors are caught and handled -- Error messages display correctly -- Retry functionality works -- User sees helpful error messages - -validation: -- Run application and trigger errors -- Test API errors -- Test file errors -- Test input errors -- Verify error messages display - -notes: -- Handle network errors (API calls) -- Handle file errors (import/export) -- Handle validation errors (inputs) -- Handle parsing errors (RSS feeds) -- Display errors in user-friendly way -- Add error logging for debugging -- Consider adding Sentry for production diff --git a/tasks/podcast-tui-app/59-theme-system.md b/tasks/podcast-tui-app/59-theme-system.md deleted file mode 100644 index 4f7ae56..0000000 --- a/tasks/podcast-tui-app/59-theme-system.md +++ /dev/null @@ -1,56 +0,0 @@ -# 59. Create Theme System Architecture - -meta: - id: podcast-tui-app-59 - feature: podcast-tui-app - priority: P1 - depends_on: [09] - tags: [theming, architecture, solidjs, design-system] - -objective: -- Design and implement a flexible theme system architecture -- Support multiple color themes (Catppuccin, Gruvbox, Tokyo, Nord, custom) -- Provide theme switching capabilities -- Ensure theme persistence across sessions -- Define theme properties (colors, fonts, spacing, borders) - -deliverables: -- `/src/themes/types.ts` - Theme type definitions -- `/src/themes/theme.ts` - Theme system core implementation -- `/src/themes/themes/` - Directory with theme files -- `/src/themes/default.ts` - Default system theme -- `/src/themes/hooks.ts` - Theme hooks (useTheme, useThemeColor) - -steps: -- Define `Theme` interface with properties: name, colors, fonts, spacing, borders -- Create theme color palettes (background, foreground, primary, secondary, accent, etc.) -- Implement `ThemeManager` class to handle theme loading, switching, and persistence -- Create `useTheme` hook for React/Solid components to access current theme -- Implement `useThemeColor` hook for accessing specific theme colors -- Add theme persistence using localStorage or file-based storage -- Export theme utilities for programmatic theme access - -tests: -- Unit: Test theme loading from JSON file -- Unit: Test theme switching updates current theme -- Unit: Test theme persistence saves/loads correctly -- Integration: Verify components update when theme changes - -acceptance_criteria: -- Theme system can load multiple theme definitions -- Theme switching works instantly across all components -- Theme preferences persist across application restarts -- Theme colors are accessible via hooks -- Theme manager handles errors gracefully - -validation: -- Run `bun run build` to verify TypeScript compilation -- Test theme switching manually in application -- Verify localStorage/file storage works -- Check all theme colors render correctly - -notes: -- Theme files should be JSON or TypeScript modules -- Use CSS variables or terminal color codes -- Consider dark/light mode compatibility -- Themes should be easily extensible diff --git a/tasks/podcast-tui-app/60-default-theme.md b/tasks/podcast-tui-app/60-default-theme.md deleted file mode 100644 index a72744c..0000000 --- a/tasks/podcast-tui-app/60-default-theme.md +++ /dev/null @@ -1,52 +0,0 @@ -# 60. Implement Default Theme (System Terminal) - -meta: - id: podcast-tui-app-60 - feature: podcast-tui-app - priority: P1 - depends_on: [59] - tags: [theming, default, solidjs, terminal] - -objective: -- Implement the default system terminal theme -- Define colors matching common terminal environments -- Ensure good readability and contrast -- Support both light and dark terminal modes - -deliverables: -- `/src/themes/themes/default.ts` - Default theme definition -- `/src/themes/themes/default-light.ts` - Light mode theme -- `/src/themes/themes/default-dark.ts` - Dark mode theme -- Updated `/src/themes/theme.ts` to load default theme - -steps: -- Define default color palette for system terminals -- Create light mode theme with standard terminal colors -- Create dark mode theme with standard terminal colors -- Ensure proper contrast ratios for readability -- Test theme in both light and dark terminal environments -- Export default theme as fallback - -tests: -- Unit: Verify default theme colors are defined -- Unit: Test theme renders correctly in light mode -- Unit: Test theme renders correctly in dark mode -- Visual: Verify text contrast meets accessibility standards - -acceptance_criteria: -- Default theme works in light terminal mode -- Default theme works in dark terminal mode -- Colors have good readability and contrast -- Theme is used as fallback when no theme selected - -validation: -- Run `bun run build` to verify TypeScript compilation -- Test in light terminal (e.g., iTerm2, Terminal.app) -- Test in dark terminal (e.g., Kitty, Alacritty) -- Check color contrast visually - -notes: -- Use standard terminal color codes (ANSI escape codes) -- Consider common terminal themes (Solarized, Dracula, etc.) -- Test on multiple terminal emulators -- Document terminal requirements diff --git a/tasks/podcast-tui-app/61-catppuccin-theme.md b/tasks/podcast-tui-app/61-catppuccin-theme.md deleted file mode 100644 index bf6cf3c..0000000 --- a/tasks/podcast-tui-app/61-catppuccin-theme.md +++ /dev/null @@ -1,52 +0,0 @@ -# 61. Add Catppuccin Theme - -meta: - id: podcast-tui-app-61 - feature: podcast-tui-app - priority: P1 - depends_on: [59] - tags: [theming, catppuccin, solidjs, popular] - -objective: -- Implement Catppuccin Mocha theme for the podcast TUI -- Provide beautiful, modern color scheme -- Ensure high contrast and readability -- Support both dark and light variants - -deliverables: -- `/src/themes/themes/catppuccin.ts` - Catppuccin theme definition -- `/src/themes/themes/catppuccin-mocha.ts` - Dark mode Catppuccin -- `/src/themes/themes/catppuccin-latte.ts` - Light mode Catppuccin -- Updated `/src/themes/themes/index.ts` to export Catppuccin themes - -steps: -- Research and implement Catppuccin Mocha color palette -- Define all color tokens (background, foreground, surface, primary, secondary, etc.) -- Create Catppuccin Mocha (dark) theme -- Create Catppuccin Latte (light) theme -- Ensure proper color contrast for accessibility -- Add Catppuccin to theme registry - -tests: -- Unit: Verify Catppuccin theme colors are defined -- Unit: Test Catppuccin Mocha renders correctly -- Unit: Test Catppuccin Latte renders correctly -- Visual: Verify Catppuccin colors are visually appealing - -acceptance_criteria: -- Catppuccin Mocha theme works in dark terminals -- Catppuccin Latte theme works in light terminals -- Colors match official Catppuccin design -- Theme is selectable from theme list - -validation: -- Run `bun run build` to verify TypeScript compilation -- Test Catppuccin theme manually in application -- Compare with official Catppuccin color palette -- Check color harmony and contrast - -notes: -- Catppuccin Mocha is the recommended dark theme -- Include all standard Catppuccin colors (latte, frappe, macchiato, mocha) -- Use official color values from Catppuccin repository -- Consider adding custom color variations diff --git a/tasks/podcast-tui-app/62-gruvbox-theme.md b/tasks/podcast-tui-app/62-gruvbox-theme.md deleted file mode 100644 index fdcf85a..0000000 --- a/tasks/podcast-tui-app/62-gruvbox-theme.md +++ /dev/null @@ -1,52 +0,0 @@ -# 62. Add Gruvbox Theme - -meta: - id: podcast-tui-app-62 - feature: podcast-tui-app - priority: P1 - depends_on: [59] - tags: [theming, gruvbox, solidjs, retro] - -objective: -- Implement Gruvbox Dark theme for the podcast TUI -- Provide warm, nostalgic color scheme -- Ensure good contrast and readability -- Support both dark and light variants - -deliverables: -- `/src/themes/themes/gruvbox.ts` - Gruvbox theme definition -- `/src/themes/themes/gruvbox-dark.ts` - Dark mode Gruvbox -- `/src/themes/themes/gruvbox-light.ts` - Light mode Gruvbox -- Updated `/src/themes/themes/index.ts` to export Gruvbox themes - -steps: -- Research and implement Gruvbox Dark theme color palette -- Define all color tokens (background, foreground, primary, secondary, etc.) -- Create Gruvbox Dark theme -- Create Gruvbox Light theme -- Ensure proper color contrast for accessibility -- Add Gruvbox to theme registry - -tests: -- Unit: Verify Gruvbox theme colors are defined -- Unit: Test Gruvbox Dark renders correctly -- Unit: Test Gruvbox Light renders correctly -- Visual: Verify Gruvbox colors are visually appealing - -acceptance_criteria: -- Gruvbox Dark theme works in dark terminals -- Gruvbox Light theme works in light terminals -- Colors match official Gruvbox design -- Theme is selectable from theme list - -validation: -- Run `bun run build` to verify TypeScript compilation -- Test Gruvbox theme manually in application -- Compare with official Gruvbox color palette -- Check color harmony and contrast - -notes: -- Gruvbox Dark is the recommended variant -- Include all standard Gruvbox colors (hard, soft, light) -- Use official color values from Gruvbox repository -- Gruvbox is popular among developers for its warmth diff --git a/tasks/podcast-tui-app/63-tokyo-theme.md b/tasks/podcast-tui-app/63-tokyo-theme.md deleted file mode 100644 index cdfd6d8..0000000 --- a/tasks/podcast-tui-app/63-tokyo-theme.md +++ /dev/null @@ -1,52 +0,0 @@ -# 63. Add Tokyo Night Theme - -meta: - id: podcast-tui-app-63 - feature: podcast-tui-app - priority: P1 - depends_on: [59] - tags: [theming, tokyo-night, solidjs, modern] - -objective: -- Implement Tokyo Night theme for the podcast TUI -- Provide modern, vibrant color scheme -- Ensure good contrast and readability -- Support both dark and light variants - -deliverables: -- `/src/themes/themes/tokyo-night.ts` - Tokyo Night theme definition -- `/src/themes/themes/tokyo-night-day.ts` - Light mode Tokyo Night -- `/src/themes/themes/tokyo-night-night.ts` - Dark mode Tokyo Night -- Updated `/src/themes/themes/index.ts` to export Tokyo Night themes - -steps: -- Research and implement Tokyo Night theme color palette -- Define all color tokens (background, foreground, primary, secondary, etc.) -- Create Tokyo Night Night (dark) theme -- Create Tokyo Night Day (light) theme -- Ensure proper color contrast for accessibility -- Add Tokyo Night to theme registry - -tests: -- Unit: Verify Tokyo Night theme colors are defined -- Unit: Test Tokyo Night Night renders correctly -- Unit: Test Tokyo Night Day renders correctly -- Visual: Verify Tokyo Night colors are visually appealing - -acceptance_criteria: -- Tokyo Night Night theme works in dark terminals -- Tokyo Night Day theme works in light terminals -- Colors match official Tokyo Night design -- Theme is selectable from theme list - -validation: -- Run `bun run build` to verify TypeScript compilation -- Test Tokyo Night theme manually in application -- Compare with official Tokyo Night color palette -- Check color harmony and contrast - -notes: -- Tokyo Night Night is the recommended variant -- Include all standard Tokyo Night colors -- Use official color values from Tokyo Night repository -- Tokyo Night is popular for its modern, clean look diff --git a/tasks/podcast-tui-app/64-nord-theme.md b/tasks/podcast-tui-app/64-nord-theme.md deleted file mode 100644 index 8f30cb9..0000000 --- a/tasks/podcast-tui-app/64-nord-theme.md +++ /dev/null @@ -1,52 +0,0 @@ -# 64. Add Nord Theme - -meta: - id: podcast-tui-app-64 - feature: podcast-tui-app - priority: P1 - depends_on: [59] - tags: [theming, nord, solidjs, minimal] - -objective: -- Implement Nord theme for the podcast TUI -- Provide clean, minimal color scheme -- Ensure good contrast and readability -- Support both dark and light variants - -deliverables: -- `/src/themes/themes/nord.ts` - Nord theme definition -- `/src/themes/themes/nord-dark.ts` - Dark mode Nord -- `/src/themes/themes/nord-light.ts` - Light mode Nord -- Updated `/src/themes/themes/index.ts` to export Nord themes - -steps: -- Research and implement Nord theme color palette -- Define all color tokens (background, foreground, primary, secondary, etc.) -- Create Nord Dark theme -- Create Nord Light theme -- Ensure proper color contrast for accessibility -- Add Nord to theme registry - -tests: -- Unit: Verify Nord theme colors are defined -- Unit: Test Nord Dark renders correctly -- Unit: Test Nord Light renders correctly -- Visual: Verify Nord colors are visually appealing - -acceptance_criteria: -- Nord Dark theme works in dark terminals -- Nord Light theme works in light terminals -- Colors match official Nord design -- Theme is selectable from theme list - -validation: -- Run `bun run build` to verify TypeScript compilation -- Test Nord theme manually in application -- Compare with official Nord color palette -- Check color harmony and contrast - -notes: -- Nord Dark is the recommended variant -- Include all standard Nord colors -- Use official color values from Nord repository -- Nord is popular for its minimalist, clean aesthetic diff --git a/tasks/podcast-tui-app/65-custom-theme.md b/tasks/podcast-tui-app/65-custom-theme.md deleted file mode 100644 index 098e3e0..0000000 --- a/tasks/podcast-tui-app/65-custom-theme.md +++ /dev/null @@ -1,56 +0,0 @@ -# 65. Implement Custom Theme Editor - -meta: - id: podcast-tui-app-65 - feature: podcast-tui-app - priority: P2 - depends_on: [59] - tags: [theming, editor, custom, solidjs] - -objective: -- Build a UI for creating and editing custom themes -- Allow users to modify color palettes -- Provide live preview of theme changes -- Save custom themes to storage - -deliverables: -- `/src/components/ThemeEditor.tsx` - Theme editor component -- `/src/components/ThemePreview.tsx` - Live theme preview component -- `/src/components/ColorPicker.tsx` - Custom color picker component -- `/src/store/theme-editor.ts` - Theme editor state management -- Updated settings screen to include theme editor - -steps: -- Create ThemeEditor component with color palette editor -- Implement ColorPicker component for selecting theme colors -- Create ThemePreview component showing live theme changes -- Build form controls for editing all theme properties -- Add save functionality for custom themes -- Implement delete functionality for custom themes -- Add validation for theme color values -- Update settings screen to show theme editor - -tests: -- Unit: Test theme editor saves custom themes correctly -- Unit: Test color picker updates theme colors -- Unit: Test theme preview updates in real-time -- Integration: Test custom theme can be loaded - -acceptance_criteria: -- Theme editor allows editing all theme colors -- Custom theme can be saved and loaded -- Live preview shows theme changes in real-time -- Custom themes persist across sessions -- Invalid color values are rejected - -validation: -- Run `bun run build` to verify TypeScript compilation -- Test theme editor manually in application -- Verify custom themes save/load correctly -- Check live preview works smoothly - -notes: -- Use existing theme type definitions -- Provide preset color palettes for quick selection -- Consider adding theme templates -- Ensure editor works on small terminal sizes diff --git a/tasks/podcast-tui-app/66-theme-selector.md b/tasks/podcast-tui-app/66-theme-selector.md deleted file mode 100644 index 8f95eb3..0000000 --- a/tasks/podcast-tui-app/66-theme-selector.md +++ /dev/null @@ -1,56 +0,0 @@ -# 66. Add Theme Selector in Settings - -meta: - id: podcast-tui-app-66 - feature: podcast-tui-app - priority: P1 - depends_on: [59, 60, 61, 62, 63, 64, 65] - tags: [theming, settings, selector, solidjs] - -objective: -- Add theme selection UI in settings screen -- Display all available themes (default, Catppuccin, Gruvbox, Tokyo, Nord, custom) -- Allow users to select and switch themes -- Show currently selected theme -- Persist theme preference - -deliverables: -- `/src/components/ThemeSelector.tsx` - Theme selector component -- Updated `/src/components/SettingsScreen.tsx` to include theme selector -- Updated `/src/store/theme.ts` to handle theme preference persistence -- Theme selector in settings navigation - -steps: -- Create ThemeSelector component with theme list -- Implement theme selection logic -- Display current theme with visual indicator -- Add theme descriptions or icons -- Integrate theme selector into Settings screen -- Save theme preference to storage -- Handle theme switching with instant updates -- Add keyboard navigation for theme list - -tests: -- Unit: Test theme selector displays all themes -- Unit: Test theme selection updates current theme -- Unit: Test theme preference saves to storage -- Integration: Test theme switching works across all components - -acceptance_criteria: -- Theme selector shows all available themes -- Users can select and switch themes -- Current theme is clearly indicated -- Theme preference persists across sessions -- Theme changes apply immediately to all components - -validation: -- Run `bun run build` to verify TypeScript compilation -- Test theme selector manually in settings -- Verify theme preference saves/loads correctly -- Check theme applies to all UI components - -notes: -- Use existing theme manager for theme switching -- Consider adding theme search/filter -- Show theme preview in selector if possible -- Ensure accessibility for keyboard navigation diff --git a/tasks/podcast-tui-app/67-browser-redirect.md b/tasks/podcast-tui-app/67-browser-redirect.md deleted file mode 100644 index ad505cd..0000000 --- a/tasks/podcast-tui-app/67-browser-redirect.md +++ /dev/null @@ -1,57 +0,0 @@ -# 67. Implement Browser Redirect Flow for OAuth - -meta: - id: podcast-tui-app-67 - feature: podcast-tui-app - priority: P2 - depends_on: [04] - tags: [oauth, authentication, browser, solidjs] - -objective: -- Implement browser redirect flow for OAuth authentication -- Handle OAuth callback in terminal application -- Exchange authorization code for access token -- Store authentication tokens securely - -deliverables: -- `/src/auth/oauth-redirect.ts` - OAuth redirect handler -- `/src/auth/oauth-callback.ts` - OAuth callback handler -- `/src/auth/token-handler.ts` - Token exchange and storage -- Updated `/src/auth/login-screen.tsx` with OAuth option -- Updated `/src/auth/authentication-state.ts` for OAuth state - -steps: -- Implement OAuth authorization URL generation with client ID/secret -- Create OAuth redirect handler to capture callback -- Handle authorization code exchange with token endpoint -- Store access token and refresh token securely -- Implement error handling for OAuth failures -- Add OAuth state parameter for security -- Update authentication state to track OAuth login status -- Add OAuth logout functionality - -tests: -- Unit: Test OAuth authorization URL generation -- Unit: Test token exchange with valid/invalid code -- Unit: Test token storage and retrieval -- Integration: Test OAuth flow from start to finish - -acceptance_criteria: -- OAuth authorization URL is generated correctly -- OAuth callback is handled without errors -- Access token is stored securely -- OAuth flow works with valid credentials -- OAuth errors are handled gracefully - -validation: -- Run `bun run build` to verify TypeScript compilation -- Test OAuth flow manually with test credentials -- Verify token storage in localStorage -- Check error handling for invalid tokens - -notes: -- Use standard OAuth 2.0 flow (authorization code grant) -- Document OAuth requirements (client ID, redirect URI) -- Consider using PKCE for enhanced security -- Test with real OAuth provider if possible -- Document limitations and requirements diff --git a/tasks/podcast-tui-app/68-qr-code-display.md b/tasks/podcast-tui-app/68-qr-code-display.md deleted file mode 100644 index 4f1a2a9..0000000 --- a/tasks/podcast-tui-app/68-qr-code-display.md +++ /dev/null @@ -1,58 +0,0 @@ -# 68. Build QR Code Display for Mobile Verification - -meta: - id: podcast-tui-app-68 - feature: podcast-tui-app - priority: P2 - depends_on: [04] - tags: [authentication, qr-code, mobile, verification, solidjs] - -objective: -- Display QR code for mobile device verification -- Generate QR code from verification URL -- Handle manual code entry as fallback -- Show verification status and expiration - -deliverables: -- `/src/components/QrCodeDisplay.tsx` - QR code display component -- `/src/utils/qr-code-generator.ts` - QR code generation utility -- `/src/auth/verification-handler.ts` - Verification flow handler -- Updated `/src/auth/login-screen.tsx` with QR code option -- Updated `/src/auth/code-validation.tsx` for manual entry - -steps: -- Integrate QR code generation library (e.g., `qrcode` package) -- Create QRCodeDisplay component with generated QR image -- Implement verification URL generation -- Add manual code entry fallback UI -- Show verification expiration timer -- Display verification success/failure status -- Handle QR code scan timeout -- Update authentication state on successful verification - -tests: -- Unit: Test QR code generation -- Unit: Test verification URL generation -- Unit: Test verification code validation -- Integration: Test complete verification flow - -acceptance_criteria: -- QR code is generated correctly from verification URL -- QR code is displayed in terminal -- Manual code entry works as fallback -- Verification status is shown clearly -- Verification expires after timeout -- Successful verification updates auth state - -validation: -- Run `bun run build` to verify TypeScript compilation -- Test QR code display manually -- Test manual code entry fallback -- Verify verification flow works end-to-end - -notes: -- Use `qrcode` or similar library for QR generation -- Display QR code in ASCII or image format -- Consider using external scanner app -- Add verification expiration (e.g., 5 minutes) -- Document mobile app requirements for scanning diff --git a/tasks/podcast-tui-app/README.md b/tasks/podcast-tui-app/README.md deleted file mode 100644 index b00fde0..0000000 --- a/tasks/podcast-tui-app/README.md +++ /dev/null @@ -1,205 +0,0 @@ -# Podcast TUI App - -Objective: Build a SolidJS-based podcast player TUI with feeds, search, player, and file sync - -Status legend: [ ] todo, [~] in-progress, [x] done - ---- - -## Phase 1: Project Foundation 🏗️ -**Setup and configure the development environment** - -- [x] 01 — Initialize SolidJS OpenTUI project with Bun → `01-project-setup.md` -- [x] 13 — Set up TypeScript configuration and build system → `13-typescript-config.md` -- [x] 14 — Create project directory structure and dependencies → `14-project-structure.md` -- [x] 15 — Build responsive layout system (Flexbox) → `15-responsive-layout.md` - -**Dependencies:** 01 -> 02 -> 03 -> 04 -> 05 -> 06 -> 07 -> 08 -> 09 -> 10 -> 11 -> 12 - ---- - -## Phase 2: Core Architecture 🏗️ -**Build the main application shell and navigation** - -- [x] 02 — Create main app shell with tab navigation → `02-core-layout.md` -- [x] 16 — Implement tab navigation component → `16-tab-navigation.md` -- [x] 17 — Add keyboard shortcuts and navigation handling → `17-keyboard-handling.md` - -**Dependencies:** 01 -> 02 -> 03 -> 04 -> 05 -> 06 -> 07 -> 08 -> 09 -> 10 -> 11 -> 12 - ---- - -## Phase 3: File Sync & Data Import/Export 💾 -**Implement direct file sync with JSON/XML formats** - -- [x] 03 — Implement direct file sync (JSON/XML import/export) → `03-file-sync.md` -- [x] 18 — Create sync data models (JSON/XML formats) → `18-sync-data-models.md` -- [x] 19 — Build import/export functionality → `19-import-export.md` -- [x] 20 — Create file picker UI for import → `20-file-picker.md` -- [x] 21 — Build sync status indicator → `21-sync-status.md` -- [x] 22 — Add backup/restore functionality → `22-backup-restore.md` - -**Dependencies:** 02 -> 03 -> 04 -> 05 -> 06 -> 07 -> 08 -> 09 -> 10 -> 11 -> 12 - ---- - -## Phase 4: Authentication System 🔐 -**Implement authentication (MUST be implemented as optional for users)** - -- [x] 04 — Build optional authentication system → `04-authentication.md` -- [x] 23 — Create authentication state (disabled by default) → `23-auth-state.md` -- [x] 24 — Build simple login screen (email/password) → `24-login-screen.md` -- [x] 25 — Implement 8-character code validation flow → `25-code-validation.md` -- [x] 26 — Add OAuth placeholder screens (document limitations) → `26-oauth-placeholders.md` -- [x] 27 — Create sync-only user profile → `27-sync-profile.md` - -**Dependencies:** 03 -> 04 -> 05 -> 06 -> 07 -> 08 -> 09 -> 10 -> 11 -> 12 - ---- - -## Phase 5: Feed Management 📻 -**Create feed data models and management UI** - -- [x] 05 — Create feed data models and types → `05-feed-management.md` -- [x] 28 — Create feed data models and types → `28-feed-types.md` -- [x] 29 — Build feed list component (public/private feeds) → `29-feed-list.md` -- [x] 30 — Implement feed source management (add/remove sources) → `30-source-management.md` -- [x] 31 — Add reverse chronological ordering → `31-reverse-chronological.md` -- [x] 32 — Create feed detail view → `32-feed-detail.md` - -**Dependencies:** 01 -> 02 -> 03 -> 04 -> 05 -> 06 -> 07 -> 08 -> 09 -> 10 -> 11 -> 12 - ---- - -## Phase 6: Search Functionality 🔍 -**Implement multi-source search interface** - -- [x] 06 — Implement multi-source search interface → `06-search.md` -- [x] 33 — Create search interface → `33-search-interface.md` -- [x] 34 — Implement multi-source search → `34-multi-source-search.md` -- [x] 35 — Add search results display → `35-search-results.md` -- [x] 36 — Build search history with persistent storage → `36-search-history.md` - -**Dependencies:** 05 -> 06 -> 07 -> 08 -> 09 -> 10 -> 11 -> 12 - ---- - -## Phase 7: Discover Feed 🌟 -**Build discover page with popular shows** - -- [x] 07 — Build discover feed with popular shows → `07-discover.md` -- [x] 37 — Create popular shows data structure → `37-popular-shows.md` -- [x] 38 — Build discover page component → `38-discover-page.md` -- [x] 39 — Add trending shows display → `39-trending-shows.md` -- [x] 40 — Implement category filtering → `40-category-filtering.md` - -**Dependencies:** 06 -> 07 -> 08 -> 09 -> 10 -> 11 -> 12 - ---- - -## Phase 8: Player Component 🎵 -**Create player UI with waveform visualization** - -- [x] 08 — Create player UI with waveform visualization → `08-player.md` -- [x] 41 — Create player UI layout → `41-player-layout.md` -- [x] 42 — Implement playback controls → `42-playback-controls.md` -- [x] 43 — Build ASCII waveform visualization → `43-waveform-visualization.md` -- [x] 44 — Add progress tracking and seek → `44-progress-tracking.md` -- [x] 45 — Implement audio integration (system/external player) → `45-audio-integration.md` - -**Dependencies:** 07 -> 08 -> 09 -> 10 -> 11 -> 12 - ---- - -## Phase 9: Settings & Configuration ⚙️ -**Build settings screen and preferences** - -- [x] 09 — Build settings screen and preferences → `09-settings.md` -- [x] 46 — Create settings screen → `46-settings-screen.md` -- [x] 47 — Add source management UI → `47-source-management-ui.md` -- [x] 48 — Build user preferences → `48-user-preferences.md` -- [x] 49 — Implement data persistence (localStorage/file-based) → `49-data-persistence.md` - -**Dependencies:** 08 -> 09 -> 10 -> 11 -> 12 - ---- - -## Phase 10: Theme System 🎨 -**Implement theming with Catppuccin, Gruvbox, Tokyo, Nord, and custom themes** - -- [x] 59 — Create theme system architecture → `59-theme-system.md` -- [x] 60 — Implement default theme (system terminal) → `60-default-theme.md` -- [x] 61 — Add Catppuccin theme → `61-catppuccin-theme.md` -- [x] 62 — Add Gruvbox theme → `62-gruvbox-theme.md` -- [x] 63 — Add Tokyo theme → `63-tokyo-theme.md` -- [x] 64 — Add Nord theme → `64-nord-theme.md` -- [ ] 65 — Implement custom theme editor → `65-custom-theme.md` -- [x] 66 — Add theme selector in settings → `66-theme-selector.md` - -**Dependencies:** 09 -> 59 -> 60 -> 61 -> 62 -> 63 -> 64 -> 65 -> 66 -> 10 - ---- - -## Phase 11: State Management & Data Layer 🗄️ -**Create global state store and data layer** - -- [x] 10 — Create global state store and data layer → `10-state-management.md` -- [x] 50 — Create global state store (Signals) → `50-global-state-store.md` -- [x] 51 — Implement API client for podcast sources → `51-api-client.md` -- [x] 52 — Add data fetching and caching → `52-data-fetching-caching.md` -- [x] 53 — Build file-based storage for sync → `53-file-based-storage.md` - -**Dependencies:** 66 -> 10 - ---- - -## Phase 12: Testing & Quality Assurance ✅ -**Set up testing framework and write comprehensive tests** - -- [ ] 11 — Set up testing framework and write tests → `11-testing.md` -- [ ] 54 — Set up testing framework (snapshot testing) → `54-testing-framework.md` -- [ ] 55 — Write component tests → `55-component-tests.md` -- [ ] 56 — Add keyboard interaction tests → `56-keyboard-tests.md` -- [x] 57 — Implement error handling → `57-error-handling.md` -- [x] 58 — Add loading states and transitions → `58-loading-states.md` - -**Dependencies:** 10 -> 11 - ---- - -## Phase 13: OAuth & External Integration 🔗 -**Complete OAuth implementation and external integrations** - -- [x] 26 — Add OAuth placeholder screens (document limitations) → `26-oauth-placeholders.md` -- [ ] 67 — Implement browser redirect flow for OAuth → `67-browser-redirect.md` -- [ ] 68 — Build QR code display for mobile verification → `68-qr-code-display.md` - -**Dependencies:** 04 -> 67 -> 68 - ---- - -## Phase 14: Deployment & Optimization 🚀 -**Optimize bundle and create documentation** - -- [ ] 12 — Optimize bundle and create documentation → `12-optimization.md` - -**Dependencies:** 11 - ---- - -## Dependencies Summary -- **Phase dependencies** ensure logical implementation order -- **Authentication MUST be implemented** (just optional for user login) -- **Theme system** is required and comes before state management - ---- - -## Exit criteria -- The feature is complete when all 58 tasks are marked [x] done -- Complete, functional podcast TUI application with all core features working -- File sync (JSON/XML) successfully imports/exports feeds and settings -- Authentication system is fully implemented (optional for users) -- Player has waveform visualization and playback controls -- All navigation and keyboard shortcuts work correctly -- Theme system works with Catppuccin, Gruvbox, Tokyo, Nord, and custom themes -- Application runs on Bun with OpenTUI diff --git a/tasks/rss-content-parsing/03-rss-content-detection.md b/tasks/rss-content-parsing/03-rss-content-detection.md new file mode 100644 index 0000000..3bec913 --- /dev/null +++ b/tasks/rss-content-parsing/03-rss-content-detection.md @@ -0,0 +1,45 @@ +# 03. Add RSS Content Type Detection + +meta: + id: rss-content-parsing-03 + feature: rss-content-parsing + priority: P2 + depends_on: [] + tags: [rss, parsing, utilities] + +objective: +- Create utility to detect if RSS feed content is HTML or plain text +- Analyze content type in description and other text fields +- Return appropriate parsing strategy + +deliverables: +- Content type detection function +- Type classification utility +- Integration points for different parsers + +steps: +1. Create `src/utils/rss-content-detector.ts` +2. Implement content type detection based on HTML tags +3. Add detection for common HTML entities and tags +4. Return type enum (HTML, PLAIN_TEXT, UNKNOWN) +5. Add unit tests for detection accuracy + +tests: +- Unit: Test HTML detection with various HTML snippets +- Unit: Test plain text detection with text-only content +- Unit: Test edge cases (mixed content, malformed HTML) + +acceptance_criteria: +- Function correctly identifies HTML vs plain text content +- Handles common HTML patterns and entities +- Returns UNKNOWN for unclassifiable content + +validation: +- Test with HTML description from real RSS feeds +- Test with plain text descriptions +- Verify UNKNOWN cases are handled gracefully + +notes: +- Look for common HTML tags:
    ,

    ,
    , , , +- Check for HTML entities: <, >, &, ", ' +- Consider content length threshold for HTML detection diff --git a/tasks/rss-content-parsing/04-html-content-extraction.md b/tasks/rss-content-parsing/04-html-content-extraction.md new file mode 100644 index 0000000..6908379 --- /dev/null +++ b/tasks/rss-content-parsing/04-html-content-extraction.md @@ -0,0 +1,47 @@ +# 04. Implement HTML Content Extraction + +meta: + id: rss-content-parsing-04 + feature: rss-content-parsing + priority: P2 + depends_on: [rss-content-parsing-03] + tags: [rss, parsing, html] + +objective: +- Parse HTML content from RSS feed descriptions +- Extract and sanitize text content +- Convert HTML to plain text for display + +deliverables: +- HTML to text conversion utility +- Sanitization function for XSS prevention +- Updated RSS parser integration + +steps: +1. Create `src/utils/html-to-text.ts` +2. Implement HTML-to-text conversion algorithm +3. Add XSS sanitization for extracted content +4. Handle common HTML elements (paragraphs, lists, links) +5. Update `parseRSSFeed()` to use new HTML parser + +tests: +- Unit: Test HTML to text conversion accuracy +- Integration: Test with HTML-rich RSS feeds +- Security: Test XSS sanitization with malicious HTML + +acceptance_criteria: +- HTML content is converted to readable plain text +- No HTML tags remain in output +- Sanitization prevents XSS attacks +- Links are properly converted to text format + +validation: +- Test with podcast descriptions containing HTML +- Verify text is readable and properly formatted +- Check for any HTML tag remnants + +notes: +- Use existing `decodeEntities()` function from rss-parser.ts +- Preserve line breaks and paragraph structure +- Convert URLs to text format (e.g., "Visit example.com") +- Consider using a lightweight HTML parser like `html-escaper` or `cheerio` diff --git a/tasks/rss-content-parsing/05-plain-text-content-handling.md b/tasks/rss-content-parsing/05-plain-text-content-handling.md new file mode 100644 index 0000000..4df0d4f --- /dev/null +++ b/tasks/rss-content-parsing/05-plain-text-content-handling.md @@ -0,0 +1,45 @@ +# 05. Maintain Plain Text Fallback Handling + +meta: + id: rss-content-parsing-05 + feature: rss-content-parsing + priority: P2 + depends_on: [rss-content-parsing-03] + tags: [rss, parsing, fallback] + +objective: +- Ensure plain text RSS feeds continue to work correctly +- Maintain backward compatibility with existing functionality +- Handle mixed content scenarios + +deliverables: +- Updated parseRSSFeed() for HTML support +- Plain text handling path remains unchanged +- Error handling for parsing failures + +steps: +1. Update `parseRSSFeed()` to use content type detection +2. Route to HTML parser or plain text path based on type +3. Add error handling for parsing failures +4. Test with both HTML and plain text feeds +5. Verify backward compatibility + +tests: +- Integration: Test with plain text RSS feeds +- Integration: Test with HTML RSS feeds +- Regression: Verify existing functionality still works + +acceptance_criteria: +- Plain text feeds parse without errors +- HTML feeds parse correctly with sanitization +- No regression in existing functionality + +validation: +- Test with various podcast RSS feeds +- Verify descriptions display correctly +- Check for any parsing errors + +notes: +- Plain text path uses existing `decodeEntities()` logic +- Keep existing parseRSSFeed() structure for plain text +- Add logging for parsing strategy selection diff --git a/tasks/rss-content-parsing/README.md b/tasks/rss-content-parsing/README.md new file mode 100644 index 0000000..6ec379d --- /dev/null +++ b/tasks/rss-content-parsing/README.md @@ -0,0 +1,18 @@ +# HTML vs Plain Text RSS Parsing + +Objective: Detect and handle both HTML and plain text content in RSS feeds + +Status legend: [ ] todo, [~] in-progress, [x] done + +Tasks +- [ ] 03 — Add content type detection utility → `03-rss-content-detection.md` +- [ ] 04 — Implement HTML content parsing → `04-html-content-extraction.md` +- [ ] 05 — Maintain plain text fallback handling → `05-plain-text-content-handling.md` + +Dependencies +- 03 -> 04 +- 03 -> 05 + +Exit criteria +- RSS feeds with HTML content are properly parsed and sanitized +- Plain text feeds continue to work as before diff --git a/tasks/text-selection-copy/01-text-selection-copy.md b/tasks/text-selection-copy/01-text-selection-copy.md new file mode 100644 index 0000000..136c34d --- /dev/null +++ b/tasks/text-selection-copy/01-text-selection-copy.md @@ -0,0 +1,45 @@ +# 01. Add Text Selection Event Handling + +meta: + id: text-selection-copy-01 + feature: text-selection-copy + priority: P2 + depends_on: [] + tags: [ui, events, clipboard] + +objective: +- Add event listeners for text selection events in the TUI components +- Detect when text is selected by the user +- Prepare infrastructure for clipboard copy on selection release + +deliverables: +- New event bus events for text selection +- Selection state tracking in app store +- Event listener setup in main components + +steps: +1. Create new event bus events for text selection (`selection.start`, `selection.end`) +2. Add selection state to app store (selectedText, selectionStart, selectionEnd) +3. Add event listeners to key components that display text +4. Track selection state changes in real-time +5. Add cleanup handlers for event listeners + +tests: +- Unit: Test event bus event emission for selection events +- Integration: Verify selection state updates when text is selected +- Manual: Select text in different components and verify state tracking + +acceptance_criteria: +- Selection events are emitted when text is selected +- Selection state is properly tracked in app store +- Event listeners are correctly registered and cleaned up + +validation: +- Run the app and select text in Player component +- Check app store selection state is updated +- Verify event bus receives selection events + +notes: +- Need to handle terminal-specific selection behavior +- Selection might not work in all terminal emulators +- Consider using OSC 52 for clipboard operations diff --git a/tasks/text-selection-copy/02-clipboard-copy-on-release.md b/tasks/text-selection-copy/02-clipboard-copy-on-release.md new file mode 100644 index 0000000..ce5610a --- /dev/null +++ b/tasks/text-selection-copy/02-clipboard-copy-on-release.md @@ -0,0 +1,45 @@ +# 02. Implement Clipboard Copy on Selection Release + +meta: + id: text-selection-copy-02 + feature: text-selection-copy + priority: P2 + depends_on: [text-selection-copy-01] + tags: [clipboard, events, user-experience] + +objective: +- Copy selected text to clipboard when selection is released +- Handle terminal clipboard limitations +- Provide user feedback when copy succeeds + +deliverables: +- Clipboard copy logic triggered on selection release +- User notifications for copy actions +- Integration with existing clipboard utilities + +steps: +1. Add event listener for selection release events +2. Extract selected text from current focus component +3. Use existing Clipboard.copy() utility +4. Emit "clipboard.copied" event for notifications +5. Add visual feedback (toast notification) + +tests: +- Unit: Test clipboard copy function with various text inputs +- Integration: Verify copy happens on selection release +- Manual: Select text and verify it appears in clipboard + +acceptance_criteria: +- Selected text is copied to clipboard when selection ends +- User receives feedback when copy succeeds +- Works across different terminal environments + +validation: +- Select text in Player description +- Verify text is in clipboard after selection ends +- Check for toast notification + +notes: +- Use existing Clipboard namespace from `src/utils/clipboard.ts` +- Consider timing of selection release vs terminal refresh +- May need to debounce copy operations diff --git a/tasks/text-selection-copy/README.md b/tasks/text-selection-copy/README.md new file mode 100644 index 0000000..6ab1a94 --- /dev/null +++ b/tasks/text-selection-copy/README.md @@ -0,0 +1,15 @@ +# Text Selection Copy to Clipboard + +Objective: When text is selected in the TUI, copy it to the clipboard on release + +Status legend: [ ] todo, [~] in-progress, [x] done + +Tasks +- [ ] 01 — Add text selection event handling → `01-text-selection-copy.md` +- [ ] 02 — Implement clipboard copy on selection release → `02-clipboard-copy-on-release.md` + +Dependencies +- 01 -> 02 + +Exit criteria +- Users can select text and it gets copied to clipboard when selection is released