commits
updated todo.txt with specific approach for phase 2: add Tags data source to view system, create tags_view.rs module following existing patterns, add tag filtering to Filter struct, update view editor. marked future nice-to-have features separately (mention network, tag cloud, etc).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
completed phase 1 of tags & mentions feature: metadata now extracted during import from channel/track descriptions. added metadata field throughout pipeline (consolidate_channels, process_r4_tracks, process_v1_tracks, merge_tracks) and app structs (Channel, Track). re-ran import generating 1,612 channels and 151,472 tracks with extracted hashtags and mentions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- created ViewInstance enum (ChannelsView, TracksView, ViewEditor)
- each pane now owns view instances instead of string IDs
- updated PaneContent to hold Vec<ViewInstance>
- removed view states from main State (now in panes)
- messages now include pane parameter for routing
- updated render_pane_content to pattern match on ViewInstance
- eliminated view ID indirection and registry lookups
- follows halloy Buffer pattern (direct ownership)
See CHANGELOG.md for detailed narrative.
- extracted channels_view.rs module (State, Message, Action)
- extracted tracks_view.rs module (State, Message, Action)
- moved filtering/sorting logic into view modules
- updated main.rs to route messages and handle Actions
- follows iced composable architecture pattern
- prepares for phase 2 (PaneContent enum) and phase 3 (DataStore)
See CHANGELOG.md for detailed narrative.
added per-view search functionality with real-time filtering:
schema changes:
- added search_query field to Filter struct (view.rs)
- removed obsolete global search_query from State
- search persists with saved views via serde
ui implementation:
- search input bar between tabs and view content
- placeholder adapts to data source ("Search channels..." / "Search tracks...")
- styled with bg2 background, gray1 border matching app palette
search behavior:
- channels: searches name + slug (case-insensitive)
- tracks: searches title + description + slug (case-insensitive)
- real-time filtering as you type
- works alongside existing filters (slug, track_count ranges)
- independent search state per view
persistence:
- search query saves to ~/.config/r4/views/*.toml
- view editor includes "Search Query (optional)" field
- editing views shows current search query
technical notes:
- reused existing matches_query() function
- updated Message::SearchChanged to include pane_id parameter
- filtering applied in render_channels_view and render_tracks_view
- fixed clippy warnings (collapsible_if, unused_variables)
all original todo items completed.
Previously when closing the last tab in a panel, the empty state message
appeared but the button to add a new view was hidden (it was part of the
tab bar which wasn't rendered). Now the tab bar is always visible,
providing consistent access to the '+ new view' button even when the
panel is empty.
moves import pipeline logic from scripts/import_data.rs into src/import.rs module
that's shared between the bin and main app. app now auto-runs import if
data/tracks.json missing.
changes:
- add [lib] to Cargo.toml with src/lib.rs
- create src/import.rs with run_pipeline() function
- update scripts/import_data.rs to call r4::import::run_pipeline()
- update src/main.rs to check for data and run import before app starts
- remove dead code: unused Channel.id field, PLEX_MONO constant,
SortField::ALL, SortDirection::ALL, delete_view function
- add #[allow(dead_code)] for search/filter functions (keeping for future)
- fix clippy lint (remove & from args)
- simplify Channel loading (direct deserialize, no intermediate struct)
tested: removing data/*.json and running app successfully imports data
change inactive tab text from gray1 to text_dim for better contrast
- replace silent error discarding with eprintln logging
- remove .expect() panics in favor of .unwrap_or_else() with fallbacks
- improve error visibility in data loading
- use .and_then() instead of nested .unwrap() for cleaner code
fixes identified in code review against CLAUDE.md guidelines:
- main.rs:224,227 - pane operations now log failures
- view.rs:102 - config_dir uses fallback instead of panic
- main.rs:61-80 - data loading now logs parse/read errors
- consolidate_channels.rs:67-78 - timestamp conversion cleaner
Each tab now has × button to remove view from panel's stack.
PaneContent.remove_view() handles index adjustment when closing.
Views are now the universal abstraction:
- Panel.view_stack holds multiple view IDs
- Panel.active_view tracks which is showing
- View.filter enables channel-specific filtering
- Click channel → creates View::channel_tracks() → adds to panel
This creates foundation for:
- Tab-based view switching per panel
- AI-generated views ("show channels by title")
- User-created views via TOML files
The flow: user clicks channel → dynamic view created/found in global
registry → pushed to panel's stack → panel renders filtered tracks
views:
- user-configurable queries on data (sort by field/direction)
- stored as .toml files in ~/.config/r4/views/
- builtins (all-channels, all-tracks) + user views loaded together
- defined by: id, name, source (Channels/Tracks), sort config
panels:
- physical pane_grid layout
- each panel references a view by ID (or None for empty)
- panels can split, resize, close, maximize independently
- default layout: 30% left (all-channels), 70% right (all-tracks)
implementation:
- removed tab bar (wrong pattern for malleable software)
- PaneContent now stores view_id: Option<String> instead of ViewType enum
- render_pane_content() looks up view and applies sort configuration
- clean separation: views define what, panels define where
also added:
- track multi-selection with ctrl/shift/cmd
- keyboard modifier tracking via subscription
- docs/views-and-panels.txt documenting the architecture
- created src/palette.rs with centralized color definitions
- defined 5 grays (173→247 rgb), red/yellow/blue/purple accents
- sidebar bg: gray4 (224,213,224)
- headers: red (163,65,51)
- selections: gray3 (204,189,204)
- all UI references palette.* instead of hardcoded colors
- refactored from custom Panel enum to iced's pane_grid
- drag divider to resize panes (handled automatically)
- wired messages for split/close/maximize (UI pending)
- cleaner architecture: pane_grid handles all layout math
- maintains two-pane layout (channels 30% | tracks 70%)
- clickable channels filter tracks reactively
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- compress source files (70MB → 20MB) and commit as .gz
- create import_data script: decompresses, runs pipeline, cleans up
- add tests for v1 track filtering (prevents duplicates)
- rename files for consistency: r4_*, v1_* prefixes
- document pipeline in docs/data.txt
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
updated todo.txt with specific approach for phase 2: add Tags data source to view system, create tags_view.rs module following existing patterns, add tag filtering to Filter struct, update view editor. marked future nice-to-have features separately (mention network, tag cloud, etc).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
completed phase 1 of tags & mentions feature: metadata now extracted during import from channel/track descriptions. added metadata field throughout pipeline (consolidate_channels, process_r4_tracks, process_v1_tracks, merge_tracks) and app structs (Channel, Track). re-ran import generating 1,612 channels and 151,472 tracks with extracted hashtags and mentions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- created ViewInstance enum (ChannelsView, TracksView, ViewEditor)
- each pane now owns view instances instead of string IDs
- updated PaneContent to hold Vec<ViewInstance>
- removed view states from main State (now in panes)
- messages now include pane parameter for routing
- updated render_pane_content to pattern match on ViewInstance
- eliminated view ID indirection and registry lookups
- follows halloy Buffer pattern (direct ownership)
See CHANGELOG.md for detailed narrative.
- extracted channels_view.rs module (State, Message, Action)
- extracted tracks_view.rs module (State, Message, Action)
- moved filtering/sorting logic into view modules
- updated main.rs to route messages and handle Actions
- follows iced composable architecture pattern
- prepares for phase 2 (PaneContent enum) and phase 3 (DataStore)
See CHANGELOG.md for detailed narrative.
added per-view search functionality with real-time filtering:
schema changes:
- added search_query field to Filter struct (view.rs)
- removed obsolete global search_query from State
- search persists with saved views via serde
ui implementation:
- search input bar between tabs and view content
- placeholder adapts to data source ("Search channels..." / "Search tracks...")
- styled with bg2 background, gray1 border matching app palette
search behavior:
- channels: searches name + slug (case-insensitive)
- tracks: searches title + description + slug (case-insensitive)
- real-time filtering as you type
- works alongside existing filters (slug, track_count ranges)
- independent search state per view
persistence:
- search query saves to ~/.config/r4/views/*.toml
- view editor includes "Search Query (optional)" field
- editing views shows current search query
technical notes:
- reused existing matches_query() function
- updated Message::SearchChanged to include pane_id parameter
- filtering applied in render_channels_view and render_tracks_view
- fixed clippy warnings (collapsible_if, unused_variables)
all original todo items completed.
moves import pipeline logic from scripts/import_data.rs into src/import.rs module
that's shared between the bin and main app. app now auto-runs import if
data/tracks.json missing.
changes:
- add [lib] to Cargo.toml with src/lib.rs
- create src/import.rs with run_pipeline() function
- update scripts/import_data.rs to call r4::import::run_pipeline()
- update src/main.rs to check for data and run import before app starts
- remove dead code: unused Channel.id field, PLEX_MONO constant,
SortField::ALL, SortDirection::ALL, delete_view function
- add #[allow(dead_code)] for search/filter functions (keeping for future)
- fix clippy lint (remove & from args)
- simplify Channel loading (direct deserialize, no intermediate struct)
tested: removing data/*.json and running app successfully imports data
- replace silent error discarding with eprintln logging
- remove .expect() panics in favor of .unwrap_or_else() with fallbacks
- improve error visibility in data loading
- use .and_then() instead of nested .unwrap() for cleaner code
fixes identified in code review against CLAUDE.md guidelines:
- main.rs:224,227 - pane operations now log failures
- view.rs:102 - config_dir uses fallback instead of panic
- main.rs:61-80 - data loading now logs parse/read errors
- consolidate_channels.rs:67-78 - timestamp conversion cleaner
Views are now the universal abstraction:
- Panel.view_stack holds multiple view IDs
- Panel.active_view tracks which is showing
- View.filter enables channel-specific filtering
- Click channel → creates View::channel_tracks() → adds to panel
This creates foundation for:
- Tab-based view switching per panel
- AI-generated views ("show channels by title")
- User-created views via TOML files
The flow: user clicks channel → dynamic view created/found in global
registry → pushed to panel's stack → panel renders filtered tracks
views:
- user-configurable queries on data (sort by field/direction)
- stored as .toml files in ~/.config/r4/views/
- builtins (all-channels, all-tracks) + user views loaded together
- defined by: id, name, source (Channels/Tracks), sort config
panels:
- physical pane_grid layout
- each panel references a view by ID (or None for empty)
- panels can split, resize, close, maximize independently
- default layout: 30% left (all-channels), 70% right (all-tracks)
implementation:
- removed tab bar (wrong pattern for malleable software)
- PaneContent now stores view_id: Option<String> instead of ViewType enum
- render_pane_content() looks up view and applies sort configuration
- clean separation: views define what, panels define where
also added:
- track multi-selection with ctrl/shift/cmd
- keyboard modifier tracking via subscription
- docs/views-and-panels.txt documenting the architecture
- refactored from custom Panel enum to iced's pane_grid
- drag divider to resize panes (handled automatically)
- wired messages for split/close/maximize (UI pending)
- cleaner architecture: pane_grid handles all layout math
- maintains two-pane layout (channels 30% | tracks 70%)
- clickable channels filter tracks reactively
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- compress source files (70MB → 20MB) and commit as .gz
- create import_data script: decompresses, runs pipeline, cleans up
- add tests for v1 track filtering (prevents duplicates)
- rename files for consistency: r4_*, v1_* prefixes
- document pipeline in docs/data.txt
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>