source_monitor 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/Gemfile.lock +1 -1
  4. data/app/assets/javascripts/source_monitor/application.js +4 -0
  5. data/app/assets/javascripts/source_monitor/controllers/confirm_navigation_controller.js +49 -0
  6. data/app/assets/javascripts/source_monitor/controllers/select_all_controller.js +36 -0
  7. data/app/controllers/source_monitor/import_sessions_controller.rb +791 -0
  8. data/app/controllers/source_monitor/sources_controller.rb +5 -36
  9. data/app/helpers/source_monitor/application_helper.rb +17 -0
  10. data/app/jobs/source_monitor/import_opml_job.rb +150 -0
  11. data/app/jobs/source_monitor/import_session_health_check_job.rb +93 -0
  12. data/app/models/source_monitor/import_history.rb +35 -0
  13. data/app/models/source_monitor/import_session.rb +34 -0
  14. data/app/views/source_monitor/import_sessions/_header.html.erb +12 -0
  15. data/app/views/source_monitor/import_sessions/_sidebar.html.erb +23 -0
  16. data/app/views/source_monitor/import_sessions/health_check/_progress.html.erb +20 -0
  17. data/app/views/source_monitor/import_sessions/health_check/_row.html.erb +44 -0
  18. data/app/views/source_monitor/import_sessions/show.html.erb +15 -0
  19. data/app/views/source_monitor/import_sessions/show.turbo_stream.erb +1 -0
  20. data/app/views/source_monitor/import_sessions/steps/_configure.html.erb +53 -0
  21. data/app/views/source_monitor/import_sessions/steps/_confirm.html.erb +121 -0
  22. data/app/views/source_monitor/import_sessions/steps/_health_check.html.erb +82 -0
  23. data/app/views/source_monitor/import_sessions/steps/_navigation.html.erb +29 -0
  24. data/app/views/source_monitor/import_sessions/steps/_preview.html.erb +172 -0
  25. data/app/views/source_monitor/import_sessions/steps/_upload.html.erb +42 -0
  26. data/app/views/source_monitor/sources/_form.html.erb +8 -138
  27. data/app/views/source_monitor/sources/_form_fields.html.erb +142 -0
  28. data/app/views/source_monitor/sources/_import_history_panel.html.erb +53 -0
  29. data/app/views/source_monitor/sources/index.html.erb +7 -1
  30. data/config/coverage_baseline.json +91 -15
  31. data/config/routes.rb +6 -0
  32. data/db/migrate/20251124090000_create_import_sessions.rb +18 -0
  33. data/db/migrate/20251124153000_add_health_fields_to_import_sessions.rb +14 -0
  34. data/db/migrate/20251125094500_create_import_histories.rb +19 -0
  35. data/lib/source_monitor/health/import_source_health_check.rb +55 -0
  36. data/lib/source_monitor/health.rb +1 -0
  37. data/lib/source_monitor/import_sessions/entry_normalizer.rb +30 -0
  38. data/lib/source_monitor/import_sessions/health_check_broadcaster.rb +103 -0
  39. data/lib/source_monitor/sources/params.rb +52 -0
  40. data/lib/source_monitor/version.rb +1 -1
  41. data/tasks/completed/codebase_audit_2025.md +1396 -0
  42. data/tasks/completed/engine-asset-configuration.md +203 -0
  43. data/tasks/completed/opml-import-wizard/opml-import-wizard-product-brief.md +58 -0
  44. data/tasks/completed/opml-import-wizard/opml-import-wizard-tech-brief.md +75 -0
  45. data/tasks/completed/opml-import-wizard/task-01/instructions.md +81 -0
  46. data/tasks/completed/opml-import-wizard/task-01/requirements.md +19 -0
  47. data/tasks/completed/opml-import-wizard/task-02/instructions.md +83 -0
  48. data/tasks/completed/opml-import-wizard/task-02/requirements.md +18 -0
  49. data/tasks/completed/opml-import-wizard/task-03/instructions.md +58 -0
  50. data/tasks/completed/opml-import-wizard/task-03/requirements.md +18 -0
  51. data/tasks/completed/opml-import-wizard/task-04/instructions.md +84 -0
  52. data/tasks/completed/opml-import-wizard/task-04/requirements.md +17 -0
  53. data/tasks/completed/opml-import-wizard/task-05/instructions.md +50 -0
  54. data/tasks/completed/opml-import-wizard/task-05/requirements.md +17 -0
  55. data/tasks/completed/opml-import-wizard/task-06/instructions.md +92 -0
  56. data/tasks/completed/opml-import-wizard/task-06/requirements.md +21 -0
  57. data/tasks/completed/phase_17_01_complexity_audit_2025-10-12.md +62 -0
  58. data/tasks/completed/phase_17_02_complexity_findings_2025-10-12.md +74 -0
  59. data/tasks/completed/phase_17_03_refactor_plan_2025-10-12.md +37 -0
  60. data/tasks/completed/phase_21_01_log_consolidation_2025-10-15.md +30 -0
  61. data/tasks/completed/release_checklist.md +23 -0
  62. data/tasks/completed/routes_refactor_evaluation.md +109 -0
  63. data/tasks/completed/source_monitor_rename_plan.md +70 -0
  64. data/tasks/completed/tasks.md +952 -0
  65. data/tasks/ideas.md +10 -0
  66. metadata +56 -3
  67. /data/tasks/{prd-setup-workflow-streamlining.md → completed/prd-setup-workflow-streamlining.md} +0 -0
  68. /data/tasks/{tasks-setup-workflow-streamlining.md → completed/tasks-setup-workflow-streamlining.md} +0 -0
@@ -0,0 +1,203 @@
1
+ # Rails Engine Asset Pipeline Guide
2
+
3
+ ## Supporting Both Sprockets and Propshaft
4
+
5
+ ### Core Principle
6
+
7
+ Support both asset pipelines to maximize compatibility with parent Rails apps. Propshaft is the modern standard, but Sprockets remains widely used in existing applications.
8
+
9
+ ---
10
+
11
+ ## Setup: Asset Bundling
12
+
13
+ Use `cssbundling-rails` and `jsbundling-rails` for maximum flexibility.
14
+
15
+ ```ruby
16
+ # Add to gemspec
17
+ gem.add_dependency "cssbundling-rails"
18
+ gem.add_dependency "jsbundling-rails"
19
+ ```
20
+
21
+ **Note:** Generators don't work inside engines. Install in a dummy app first, then manually move files to the engine.
22
+
23
+ ```bash
24
+ cd test/dummy
25
+ rails javascript:install:esbuild
26
+ rails css:install:tailwind
27
+ # Move generated files back to engine root
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Asset Organization
33
+
34
+ ### Directory Structure
35
+
36
+ ```
37
+ app/assets/
38
+ ├── builds/ENGINE_NAME/ # Bundled CSS/JS (auto-exposed)
39
+ ├── images/ENGINE_NAME/ # Image files
40
+ ├── svgs/ENGINE_NAME/ # SVG files
41
+ └── config/ # Manifest files (optional)
42
+ ```
43
+
44
+ **Rule:** Always namespace asset directories with your engine name to prevent conflicts.
45
+
46
+ ---
47
+
48
+ ## Configuring engine.rb
49
+
50
+ ### Basic Setup (Works for Both Pipelines)
51
+
52
+ ```ruby
53
+ module YourEngine
54
+ class Engine < ::Rails::Engine
55
+ isolate_namespace YourEngine
56
+
57
+ initializer "your_engine.assets" do |app|
58
+ if app.config.respond_to?(:assets)
59
+ # Expose asset directories
60
+ app.config.assets.paths << Engine.root.join("app", "assets", "builds").to_s
61
+ app.config.assets.paths << Engine.root.join("app", "assets", "images").to_s
62
+ app.config.assets.paths << Engine.root.join("app", "assets", "svgs").to_s
63
+ end
64
+ end
65
+ end
66
+ end
67
+ ```
68
+
69
+ This configuration:
70
+
71
+ - Exposes the `builds` directory (bundled assets)
72
+ - Exposes the `images` directory
73
+ - Exposes the `svgs` directory
74
+ - Works automatically with Propshaft
75
+ - Requires additional configuration for Sprockets (see below)
76
+
77
+ ---
78
+
79
+ ## Sprockets-Specific Configuration
80
+
81
+ For Sprockets to properly precompile assets referenced in CSS (via `url()`), use one of these approaches:
82
+
83
+ ### Option 1: Programmatic Precompile (Recommended)
84
+
85
+ ```ruby
86
+ initializer "your_engine.assets" do |app|
87
+ if app.config.respond_to?(:assets)
88
+ # Add paths
89
+ app.config.assets.paths << Engine.root.join("app", "assets", "builds").to_s
90
+ app.config.assets.paths << Engine.root.join("app", "assets", "images").to_s
91
+ app.config.assets.paths << Engine.root.join("app", "assets", "svgs").to_s
92
+
93
+ # Configure precompilation for Sprockets
94
+ asset_paths = [
95
+ ["app", "assets", "images"],
96
+ ["app", "assets", "images", "your_engine"],
97
+ ["app", "assets", "svgs"],
98
+ ]
99
+
100
+ paths_to_precompile = asset_paths.flat_map do |path|
101
+ Dir[Engine.root.join(*path, "**", "*")].filter_map do |file|
102
+ next unless File.file?(file)
103
+ Pathname.new(file).relative_path_from(Engine.root.join(*path)).to_s
104
+ end
105
+ end
106
+
107
+ app.config.assets.precompile += paths_to_precompile
108
+ end
109
+ end
110
+ ```
111
+
112
+ **Critical:** Paths must be relative from the asset directory (e.g., `your_engine/logo.png`, not `app/assets/images/your_engine/logo.png`).
113
+
114
+ ### Option 2: Manual Precompile Array
115
+
116
+ ```ruby
117
+ initializer "your_engine.assets" do |app|
118
+ if app.config.respond_to?(:assets)
119
+ app.config.assets.paths << Engine.root.join("app", "assets", "images").to_s
120
+ app.config.assets.precompile += %w[ your_engine/logo.png your_engine/icon.svg ]
121
+ end
122
+ end
123
+ ```
124
+
125
+ ### Option 3: Manifest File
126
+
127
+ Create `app/assets/config/your_engine_manifest.js`:
128
+
129
+ ```javascript
130
+ //= link_tree ../builds
131
+ //= link_tree ../images
132
+ //= link_tree ../svgs
133
+ ```
134
+
135
+ Then in `engine.rb`:
136
+
137
+ ```ruby
138
+ initializer "your_engine.assets" do |app|
139
+ if defined?(::Sprockets)
140
+ app.config.assets.precompile += %w[your_engine_manifest.js]
141
+ end
142
+ end
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Using Assets
148
+
149
+ ### In Views
150
+
151
+ ```erb
152
+ <%= image_tag 'your_engine/logo.png' %>
153
+ <%= image_tag 'your_engine/icon.svg' %>
154
+ ```
155
+
156
+ ### In CSS
157
+
158
+ ```css
159
+ .logo {
160
+ background-image: url("your_engine/logo.png");
161
+ }
162
+ ```
163
+
164
+ **Propshaft behavior:** Automatically finds assets and adds digest hashes.
165
+ **Sprockets behavior:** Requires files to be in the precompile array.
166
+
167
+ ---
168
+
169
+ ## Key Differences Between Pipelines
170
+
171
+ | Aspect | Propshaft | Sprockets |
172
+ | ------------------------ | ------------------------ | --------------------------------- |
173
+ | Asset finding | Automatic from paths | Requires precompile configuration |
174
+ | CSS url() references | Works automatically | Needs explicit precompile setup |
175
+ | Configuration complexity | Minimal | More involved |
176
+ | Prefix flexibility | Can work with or without | Requires namespace prefix |
177
+
178
+ ---
179
+
180
+ ## Testing Checklist
181
+
182
+ - [ ] Test in a Sprockets-based app
183
+ - [ ] Test in a Propshaft-based app
184
+ - [ ] Verify `image_tag` works in views
185
+ - [ ] Verify `url()` references work in CSS
186
+ - [ ] Confirm digest hashes are applied in production
187
+ - [ ] Check for asset path conflicts with parent app
188
+
189
+ ---
190
+
191
+ ## Quick Decision Tree
192
+
193
+ **Q: Do bundled assets in `app/assets/builds` need configuration?**
194
+ A: No, they're auto-exposed to both pipelines.
195
+
196
+ **Q: Do images/SVGs need configuration?**
197
+ A: Yes. Add paths with `app.config.assets.paths <<`. For Sprockets, also add to precompile array.
198
+
199
+ **Q: Are CSS `url()` references working?**
200
+ A: Propshaft: automatic. Sprockets: add files to precompile array.
201
+
202
+ **Q: Should I namespace asset directories?**
203
+ A: Yes, always use `your_engine/` prefix to prevent conflicts.
@@ -0,0 +1,58 @@
1
+ ## Objective
2
+
3
+ Enable users to bulk import feed sources via a wizard workflow that guides them through uploading an OPML file, previewing and selecting sources, running health checks, and configuring settings before importing—all from the Sources index page.
4
+
5
+ ## Project Goal
6
+
7
+ Allow users to successfully import at least 90% of valid, healthy feeds from an OPML file in a single workflow, with less than 5% error rate due to invalid or duplicate sources.
8
+
9
+ ## User Journey
10
+
11
+ 1. **Entry Point**
12
+ - User navigates to the Sources index page.
13
+ - User clicks the new "Import OPML" button.
14
+
15
+ 2. **Step 1: Upload OPML**
16
+ - User is presented with a wizard step to upload an OPML file.
17
+ - The system accepts any OPML file and parses it, handling errors gracefully.
18
+
19
+ 3. **Step 2: Preview & Select Sources**
20
+ - User sees a table listing all parsed sources from the OPML file.
21
+ - The table includes a column indicating whether each source is "Already Imported."
22
+ - Filters at the top allow toggling between "All," "New Sources," and "Existing Sources."
23
+ - User can unselect any feeds they do not wish to import.
24
+ - Selections persist as the user navigates between wizard steps.
25
+
26
+ 4. **Step 3: Health Check**
27
+ - The system runs a health check on each selected source’s feed URL.
28
+ - Results are displayed in a table with a green check for healthy sources and a red X for unhealthy sources.
29
+ - Unhealthy sources are unselected by default, but users can re-select them and proceed if desired.
30
+
31
+ 5. **Step 4: Configure Settings**
32
+ - User is presented with source settings (same options as single feed creation) to apply uniformly to all selected sources.
33
+ - No per-feed override; settings apply to all selected feeds.
34
+
35
+ 6. **Step 5: Confirm & Import**
36
+ - User reviews the list of sources to be imported and confirms.
37
+ - Import is initiated as a background job.
38
+ - User is redirected to the Sources index page with a static confirmation message listing imported sources.
39
+ - Fetching results and source health updates are shown in real-time via existing UI features.
40
+
41
+ ## Features
42
+
43
+ ### In Scope
44
+ - OPML File Upload
45
+ - Wizard Workflow (Multi-step)
46
+ - Source Preview Table with Filters
47
+ - Duplicate Source Detection
48
+ - Source Selection Persistence
49
+ - Health Check for Feeds
50
+ - Bulk Source Settings Configuration
51
+ - Background Job for Import
52
+ - Static Import Confirmation
53
+
54
+ ### Out of Scope
55
+ - Per-feed Settings Overrides
56
+ - Real-time Import Progress Bar
57
+ - OPML File Size or Structure Restrictions
58
+ - Advanced Error Recovery for Import Failures
@@ -0,0 +1,75 @@
1
+ ### Technical Summary
2
+
3
+ Implement a multi-step OPML import wizard accessible from the Sources index page, enabling admin users to upload an OPML file, preview and select sources, run health checks, configure bulk settings, and confirm import, with results stored for later viewing as import history.
4
+ This extends SourceMonitor’s existing sources management, background job orchestration (Solid Queue), Turbo Streams real-time UI, and follows established Rails engine, controller, and asset pipeline patterns.
5
+
6
+ ### System Design
7
+
8
+ #### **Frontend**
9
+ - Add "Import OPML" button to the Sources index page, linking to a dedicated wizard route.
10
+ - Wizard UI uses a sidebar for step navigation (Upload, Preview, Health Check, Configure, Confirm), with current step highlighted.
11
+ - Each step is rendered as a separate view, using Turbo Frames for partial updates and state transitions.
12
+ - File upload uses standard Rails form helpers, with immediate synchronous parsing and error feedback.
13
+ - Preview table paginates for large source lists, supports filters ("All", "New Sources", "Existing Sources"), and disables selection for duplicates/malformed entries.
14
+ - Selection state and bulk settings persist across steps using a temporary ImportSession record.
15
+ - Health check results update in real-time via Turbo Streams; unhealthy sources are unselected by default but can be re-selected.
16
+ - Bulk settings form reuses the single source creation partial, applying settings uniformly to all selected sources.
17
+ - Confirmation step displays a summary table of sources and settings; import progress and results are shown via Turbo Streams and static messaging.
18
+ - Accessibility, Tailwind styling, and Turbo conventions are followed throughout.
19
+
20
+ #### **Backend**
21
+ - Introduce an ImportSession model to persist wizard state (uploaded file, parsed sources, selections, settings) per user/session.
22
+ - OPML file is parsed synchronously in the controller; malformed entries are marked and excluded from selection.
23
+ - Duplicate detection matches only on feed URL, referencing existing sources in the database.
24
+ - Health checks for selected sources are enqueued as individual Solid Queue jobs; results are stored and broadcast via Turbo Streams.
25
+ - Import confirmation triggers a background job that creates sources individually, reporting errors per source and skipping duplicates.
26
+ - Import job results (successes, failures, skipped duplicates) are stored in an ImportHistory record for later viewing.
27
+ - Controllers follow SourceMonitor’s established RESTful and Turbo Stream response patterns, with strong parameter sanitization and error handling.
28
+ - If the user refreshes or exits the wizard, the ImportSession is discarded and progress is lost; navigation away triggers a JS warning.
29
+
30
+ #### **Data & Storage**
31
+ - Add `import_sessions` table to persist wizard state (user reference, OPML file metadata, parsed sources, selections, bulk settings, step state).
32
+ - Add `import_histories` table to store completed import results (timestamp, user, sources imported, failures, skipped duplicates, error details).
33
+ - No changes to existing `sourcemon_sources` table; duplicate detection uses feed URL matching.
34
+ - All new tables follow SourceMonitor’s naming, indexing, and migration conventions.
35
+
36
+ #### **Background Jobs**
37
+ - Health check jobs use existing Solid Queue infrastructure, leveraging SourceMonitor’s health check logic.
38
+ - Import job creates sources individually, logs errors per source, and updates ImportHistory.
39
+ - Job orchestration, retry, and error handling follow established patterns in app/jobs/source_monitor/*.
40
+
41
+ #### **Integrations**
42
+ - No new external integrations; leverages existing Feedjira (OPML parsing), Faraday (feed health checks), and Turbo Streams (real-time UI).
43
+ - All background jobs and real-time updates use Solid Queue and Solid Cable/Redis as configured.
44
+
45
+ #### **Security**
46
+ - Wizard and import actions restricted to authenticated admin users via SourceMonitor’s authentication hooks.
47
+ - All data access and mutations use strong parameter sanitization and follow engine security conventions.
48
+
49
+ #### **Testing**
50
+ - Unit, integration, and system tests cover wizard flow, OPML parsing, selection logic, health checks, bulk settings, import job, and import history.
51
+ - Use Minitest and VCR/WebMock for HTTP and feed parsing fixtures.
52
+ - Regression tests for edge cases: malformed OPML, all sources already imported, all health checks fail, large file uploads, concurrent imports.
53
+ - Turbo Stream and accessibility behaviors validated in system tests.
54
+
55
+ ### Data Model / Schema Changes
56
+
57
+ | Table | Column | Type | Description |
58
+ |--------------------|----------------------|--------------|--------------------------------------------------------------------|
59
+ | `import_sessions` | `user_id` | UUID (FK) | References the admin user running the wizard |
60
+ | | `opml_file_metadata` | JSONB | Stores OPML file info (filename, size, upload timestamp) |
61
+ | | `parsed_sources` | JSONB | Array of parsed sources with status (valid, malformed, duplicate) |
62
+ | | `selected_source_ids`| JSONB | Array of selected source identifiers |
63
+ | | `bulk_settings` | JSONB | Bulk source settings to apply |
64
+ | | `current_step` | String | Tracks wizard step for session |
65
+ | | `created_at` | Timestamp | Creation time |
66
+ | | `updated_at` | Timestamp | Last update time |
67
+ | `import_histories` | `user_id` | UUID (FK) | References the admin user who performed the import |
68
+ | | `imported_sources` | JSONB | Array of successfully imported sources |
69
+ | | `failed_sources` | JSONB | Array of sources that failed to import with error details |
70
+ | | `skipped_duplicates` | JSONB | Array of sources skipped due to duplication |
71
+ | | `bulk_settings` | JSONB | Settings applied to imported sources |
72
+ | | `started_at` | Timestamp | Import start time |
73
+ | | `completed_at` | Timestamp | Import completion time |
74
+
75
+ All new tables and columns follow SourceMonitor’s migration, indexing, and naming standards.
@@ -0,0 +1,81 @@
1
+ - **Context**
2
+ - Implement the entrypoint and shell for the multi-step OPML Import Wizard described in the PRD / Tech Brief. The wizard is launched from the Sources index page and contains steps: Upload, Preview, Health Check, Configure, Confirm. The shell must provide navigation UI, per‑step persistence, JS guard against accidental navigation/refresh, admin-only access, and server-side incremental persistence via a new ImportSession model (integer user_id, JSONB fields).
3
+ - This task does NOT implement file parsing, preview table, health checks, settings form, or the import/background job — those are separate tasks that depend on this shell.
4
+
5
+ - **Goals**
6
+ - Add an "Import OPML" action on the Sources index that navigates to a dedicated wizard route.
7
+ - Create a multi-step wizard shell: left sidebar listing steps (Upload, Preview, Health Check, Configure, Confirm) with current step highlighted, and the main content area rendered per step.
8
+ - Provide per-step navigation with state persisted server-side to an ImportSession record so subsequent steps can update state.
9
+ - Show a browser JS warning when the user attempts to refresh or navigate away from the wizard.
10
+ - Provide a cancel/exit action that deletes the ImportSession and returns the user to the Sources index.
11
+ - Enforce admin-only access on the wizard endpoints and follow engine UI conventions (Tailwind, accessibility, Turbo Frames).
12
+
13
+ - **Technical Guidelines**
14
+ - Routes & Controller
15
+ - Add engine routes for the wizard under the engine namespace (e.g., GET/POST /import_opml or /sources/import_opml) exposing step navigation. Use a Wicked-style incremental pattern (one controller that renders step views based on a step param or separate RESTful actions per step) and render step content inside Turbo Frames.
16
+ - Controller must enforce admin-only access using the engine’s existing authentication hooks (follow patterns used by SourcesController).
17
+ - Provide controller actions:
18
+ - new / create to start a new ImportSession and redirect to the first step,
19
+ - show/edit endpoints for step rendering and updates (or a step param on a single action),
20
+ - cancel/destroy to delete ImportSession and redirect to Sources index.
21
+ - All form submissions/step transitions should save partial state to ImportSession (see model below) and respond with Turbo Frame updates.
22
+ - ImportSession model & migration
23
+ - Implement ImportSession ActiveRecord model and DB migration following engine conventions.
24
+ - Use Rails’ default integer primary keys and integer user reference: t.references :user, foreign_key: true (not UUID).
25
+ - Include JSONB columns for server-side state:
26
+ - opml_file_metadata (JSONB)
27
+ - parsed_sources (JSONB) — array of parsed entries with status
28
+ - selected_source_ids (JSONB) — array of selected parsed entry identifiers
29
+ - bulk_settings (JSONB)
30
+ - current_step (string)
31
+ - Add timestamps (created_at, updated_at).
32
+ - Ensure migration and model naming follow repository conventions and include indices if needed for lookups by user.
33
+ - Views / UI Shell
34
+ - Add an "Import OPML" button/link to the Sources index page that navigates to the wizard entry route. Follow existing Sources index patterns (use Turbo Frame if appropriate).
35
+ - Create a wizard layout view that:
36
+ - Renders a left sidebar listing steps (Upload, Preview, Health Check, Configure, Confirm), highlights the active step, and uses accessible semantics (nav, aria-current on active step).
37
+ - Renders the step content region inside a named Turbo Frame so step content can be replaced without full-page reloads.
38
+ - Follows Tailwind styling and the engine’s accessibility patterns.
39
+ - Each step should be a separate partial/view (initially stubbed with placeholders) rendered into the Turbo Frame. The Upload step must include the file input form that posts to the ImportSession controller (actual parsing implemented in another task).
40
+ - Client behavior and navigation safety
41
+ - Implement a browser JS beforeunload warning when the user is in the wizard to prevent accidental refresh/navigate away. Use unobtrusive Stimulus/Turbolinks/Turbo patterns consistent with the repo (avoid breaking existing global behaviors).
42
+ - Provide explicit Cancel/Exit action in the UI which triggers server-side deletion of the ImportSession and redirects to Sources index. The Cancel action must not require the beforeunload confirmation to proceed.
43
+ - Ensure per-step navigation (next/back links) saves state to ImportSession via standard Rails form submissions or Turbo Frame form submissions so selections/text persist while the ImportSession exists.
44
+ - Persistence semantics & edge conditions
45
+ - Persist wizard state incrementally to ImportSession on each step transition or save action.
46
+ - Enforce admin-only access on import session resources.
47
+ - Do not implement parsing/preview/health logic here; parsed_sources may be initialized empty; Upload step should accept a file param and store metadata in opml_file_metadata (actual parsing implemented in next task).
48
+ - The UI should be designed so if the user refreshes (explicit browser reload), behavior conforms to the PRD requirement: show a fresh wizard state (documented for the implementing developer). Also show in UI a JS warning to prevent accidental refresh. (Concrete refresh reset behavior will be implemented per higher-level product decision when wiring parsing/session expiry.)
49
+ - Conventions & quality
50
+ - Use existing Turbo Frames, Turbo Streams and presenter patterns used across the engine. Use Tailwind classes and existing layout components for consistency.
51
+ - Sanitize and permit only required params in controller (use engine security/parameter sanitizer patterns).
52
+ - Add basic unit/controller/view tests to assert:
53
+ - Route and action presence,
54
+ - ImportSession creation with user_id integer,
55
+ - Cancel deletes the session and redirects,
56
+ - Sidebar renders with active step highlight,
57
+ - beforeunload warning is registered while wizard open.
58
+ - Internationalization/messages: follow repo pattern (simple English strings ok if consistent with existing views).
59
+ - Add migration, model, controller, views and routes in same PR; keep implementation minimal and extensible for follow-on tasks.
60
+
61
+ - **Out of scope**
62
+ - Do not implement OPML parsing, source preview table, duplicate detection, health checks, bulk settings form fields, or background import job logic in this task.
63
+ - Do not implement persistence lifecycles beyond basic create/update/delete of ImportSession (e.g., expiry policies, garbage collection of sessions, or advanced session reconciliation).
64
+ - Do not implement per-feed settings overrides, real-time import progress bar, or import history viewing (those are separate tasks).
65
+ - Do not change sourcemon_sources table or alter existing source creation logic here.
66
+
67
+ - **Suggested research**
68
+ - Inspect Sources index and its patterns to add the Import OPML entrypoint:
69
+ - app/views/source_monitor/sources/index.html.erb
70
+ - app/controllers/source_monitor/sources_controller.rb
71
+ - Review engine routing and controller conventions:
72
+ - config/routes.rb (engine)
73
+ - app/controllers/source_monitor/application_controller.rb (auth pattern)
74
+ - Review turbo frame and presenter/responder patterns:
75
+ - lib/source_monitor/turbo_streams/stream_responder.rb
76
+ - app/assets/javascripts/source_monitor/turbo_actions.js
77
+ - app/views/layouts/source_monitor/application.html.erb
78
+ - Find examples of model/migration conventions and JSONB usage:
79
+ - existing migrations under db/migrate/ and models using JSONB (e.g., sourcemon_* tables referenced in research)
80
+ - Authentication hooks / admin-only enforcement:
81
+ - lib/source_monitor/configuration.rb and controller auth checks used in SourcesController (follow same authorization approach).
@@ -0,0 +1,19 @@
1
+ #### Goals
2
+
3
+ - Add an entry point on the Sources index page labeled "Import OPML" that navigates to a dedicated wizard route.
4
+ - Implement a multi-step wizard shell with a left sidebar listing steps (Upload, Preview, Health Check, Configure, Confirm) and highlighting the current step.
5
+ - Provide persistent per-step navigation and a JS warning when the user attempts to refresh or navigate away.
6
+ - Implement server-side incremental persistence for wizard state via an ImportSession ActiveRecord model so later steps can update state.
7
+
8
+ #### Technical Considerations
9
+
10
+ - Use a dedicated engine route and controller that follow a Wicked-style incremental update pattern for steps and render step content inside Turbo Frames.
11
+ - Persist wizard state to an ImportSession record. Implement the ImportSession model and migration to reference the host user using standard integer-based ActiveRecord IDs (i.e., integer primary keys and a user reference using integer IDs). Ensure migrations use the default Rails integer id conventions (for example, t.references :user, foreign_key: true or equivalent) rather than UUID columns.
12
+ - Store core placeholders in ImportSession (opml_file_metadata, parsed_sources, selected_source_ids, bulk_settings, current_step) as JSONB where appropriate.
13
+ - Enforce admin-only access checks using existing SourceMonitor authentication hooks.
14
+ - Provide graceful cancel/exit behavior that deletes the ImportSession and returns the user to the Sources index.
15
+ - UI/UX must follow engine conventions: Tailwind styling, accessibility, and Turbo/Turbo Frames patterns used elsewhere in the codebase.
16
+
17
+ #### Dependencies
18
+
19
+ - None
@@ -0,0 +1,83 @@
1
+ **Context**
2
+
3
+ - This task implements the "Upload" step of the OPML import wizard for the SourceMonitor engine.
4
+ - The wizard shell and ImportSession persistence are provided by the wizard shell task; this task must wire the upload form and synchronous parsing into that flow.
5
+ - Parsing should use the repo's existing feed parsing libraries (Feedjira/Nokogiri patterns) and persist parsed results into ImportSession.parsed_sources (JSONB). Malformed entries must be flagged and excluded from selection in the Preview step.
6
+
7
+ **Goals**
8
+
9
+ - Add an upload form for the Upload step that accepts an OPML file and validates file type before parsing.
10
+ - Parse the uploaded OPML file synchronously in the controller action handling the Upload step.
11
+ - Extract feed entries and persist them into ImportSession.parsed_sources as JSONB.
12
+ - Mark malformed/unparsable entries with a status and an error message so they are not selectable in Preview.
13
+ - Prevent navigation to Preview until ImportSession.parsed_sources contains at least one valid parsed entry; surface actionable error messages for invalid file / parse failures.
14
+ - Follow SourceMonitor engine conventions (strong params, user scoping, security, Tailwind/Turbo forms).
15
+
16
+ **Technical Guidelines**
17
+
18
+ - Controller & route
19
+ - Implement a POST action on the wizard Upload step controller (the wizard route provided by the shell) that receives the uploaded file and the ImportSession identifier (or creates/loads ImportSession for current admin).
20
+ - Enforce admin-only access via existing SourceMonitor authentication hooks.
21
+ - Use strong parameter sanitization for the file param and ImportSession id.
22
+
23
+ - File handling & validation
24
+ - Accept standard Rails file upload types (ActionDispatch::Http::UploadedFile). Validate the upload exists and is non-empty.
25
+ - Perform a content-type check for XML/OPML (e.g., application/xml, text/xml, text/x-opml, application/opml) but do not rely solely on content-type: if content-type is absent or generic, attempt XML parsing anyway.
26
+ - Read the uploaded file via tempfile or uploaded_file.read in a memory-conscious way. Follow engine file handling safety conventions (tempfiles, no persistent file writes unless required by ImportSession metadata).
27
+ - Persist basic OPML file metadata into ImportSession.opml_file_metadata (filename, size, uploaded_at).
28
+
29
+ - Synchronous parsing
30
+ - Parse synchronously in the controller request handling the Upload step—do not enqueue parsing to background jobs.
31
+ - Reuse existing parsing patterns: prefer Feedjira for feed handling when applicable and Nokogiri for XML traversal (OPML is XML). Use Feedjira/Nokogiri idioms already present in the repo (inspect lib/source_monitor/fetching/feed_fetcher.rb and feedjira init).
32
+ - Traverse OPML outlines to extract feed entries; for each candidate entry, normalize and extract at minimum: feed URL (required), title (if present), website/HTML URL (if present), and any obvious metadata.
33
+ - For entries that cannot be parsed (malformed XML snippet, missing feed URL, invalid URL), produce an entry object with a status (e.g., "malformed") and an error message describing the issue.
34
+ - For successfully parsed entries produce an entry object with status "valid" and extracted fields. Do not perform duplicate detection here (preview task handles duplicates), but include the feed_url so preview can detect duplicates by comparing against existing sourcemon_sources.
35
+
36
+ - Persisting parsed results
37
+ - Store the array of parsed entry objects into ImportSession.parsed_sources (JSONB). Also update ImportSession.opml_file_metadata and ImportSession.current_step if appropriate.
38
+ - Parsed entries must include:
39
+ - stable temporary id or index (so UI can refer to rows)
40
+ - feed_url (string)
41
+ - title (string|null)
42
+ - website_url (string|null)
43
+ - status: "valid" | "malformed"
44
+ - error: string (present when status == "malformed")
45
+ - any other minimal metadata useful to Preview (e.g., raw_outline_index, line/position optional)
46
+ - Ensure ImportSession is scoped to the current user (user_id integer) and use standard ActiveRecord integer primary key conventions (do not change user id type).
47
+
48
+ - Errors & UX constraints
49
+ - If the uploaded file is not valid XML / OPML, return an actionable error message on the Upload view (do not crash). Do not advance to Preview.
50
+ - If parsing succeeds but yields zero valid entries, block progression to Preview and show a clear message instructing the user to upload a different OPML or correct the file.
51
+ - If parsing yields some valid entries, persist them and redirect/render the Preview step UI via Turbo Frames (the wizard shell controls the step rendering). Ensure selections are not created yet—only parsed_sources persisted.
52
+
53
+ - Security & limits
54
+ - Respect standard engine security: only admin users can upload; operate on ImportSession scoped to current user.
55
+ - Use tempfile APIs provided by Rails for file reading; do not dangerously evaluate XML. Use safe Nokogiri parsing options (no external entity expansion).
56
+ - Do not implement custom file size limits in this task (out of scope), but parse in a way that does not load huge files into excessive memory when possible.
57
+
58
+ - Testing guidance (what to cover)
59
+ - Controller tests for successful upload and parsing storing parsed_sources with both valid and malformed entries.
60
+ - Tests for invalid file types and malformed XML returning appropriate errors and not advancing to Preview.
61
+ - Verify ImportSession is updated (opml_file_metadata and parsed_sources) and is scoped to current user.
62
+ - Use existing repo test helpers and VCR/webmock patterns if parsing makes external requests (should not for pure OPML parsing).
63
+
64
+ **Out of scope**
65
+
66
+ - Preview UI/table rendering, filters or pagination (Preview task handles these).
67
+ - Duplicate detection or marking duplicates as part of parsing (Preview task handles matching feed_url against existing sourcemon_sources).
68
+ - Backgrounding parsing or streaming large-file parsing—parsing must be synchronous per requirements.
69
+ - Per-feed settings, health checks, or import confirmation logic.
70
+ - File storage beyond keeping metadata in ImportSession.opml_file_metadata.
71
+
72
+ **Suggested research (inspect before implementing)**
73
+
74
+ - Existing feed parsing patterns:
75
+ - lib/source_monitor/fetching/feed_fetcher.rb (Feedjira usage and parsing patterns)
76
+ - config/initializers/feedjira.rb (any repo customizations)
77
+ - Wizard shell controller/layout and ImportSession model/location created by the wizard shell task:
78
+ - Routes and controller names for the wizard Upload step (app/controllers/... likely under source_monitor/import_sessions or import_wizard controller).
79
+ - ImportSession model/migration shape (fields: parsed_sources JSONB, opml_file_metadata JSONB, user_id integer).
80
+ - Existing examples of file upload handling and strong param patterns across the engine (SourcesController create/update forms and controllers under app/controllers/source_monitor).
81
+ - Nokogiri safe parsing patterns used in repo (search for Nokogiri usage) to avoid unsafe XML parsing options.
82
+
83
+ Implement only the Upload step controller action, the upload form view for the Upload step, synchronous parsing and persistence to ImportSession.parsed_sources, and the error/flow control that blocks Preview until at least one valid parsed entry exists.
@@ -0,0 +1,18 @@
1
+ #### Goals
2
+
3
+ - Implement the Upload step form that accepts OPML files and validates file type before parsing.
4
+ - Parse OPML synchronously in the request, extract feed entries, and persist parsed entries into the ImportSession.parsed_sources JSONB field.
5
+ - Mark malformed or unparsable entries clearly (status + error message) so they are excluded from selection in Preview.
6
+ - Block navigation to the Preview step until at least one valid parsed entry exists.
7
+
8
+ #### Technical Considerations
9
+
10
+ - Use existing Feedjira/Nokogiri parsing patterns where available; parse synchronously in controller action handling the Upload step and store parsed results in ImportSession.parsed_sources.
11
+ - Validate content-type and fallback to XML parsing based on file contents; present actionable error messages for invalid files or malformed XML.
12
+ - Ensure the ImportSession model and migration reference the user via standard integer-based ActiveRecord IDs (i.e., integer primary keys and an integer user reference). Do not introduce UUID columns for user references—use the Rails default integer id and foreign key conventions.
13
+ - For large files, ensure the request handles a reasonable file size and the preview UI will paginate large parsed lists (pagination handled in Preview task). Synchronous parsing chosen deliberately for immediate feedback.
14
+ - Respect security and file handling conventions in the engine (tempfiles, permitted params, user scoping).
15
+
16
+ #### Dependencies
17
+
18
+ - Implement OPML Import Wizard Shell (needed for routes, ImportSession persistence and step navigation)
@@ -0,0 +1,58 @@
1
+ - **Context**
2
+ - This task implements the Preview step of the OPML import wizard (part of the "Preview and Select Sources" user story). The Upload step will have populated ImportSession.parsed_sources (JSONB) with entries parsed from the OPML and ImportSession exists for the current admin user. The engine uses Turbo Frames, Turbo Streams, Tailwind, server-side pagination helpers, and a Source model/table (sourcemon_sources) for duplicate detection. Selection state for the wizard must persist in ImportSession.selected_source_ids (JSONB) while the ImportSession exists.
3
+
4
+ - **Goals**
5
+ - Render a paginated Preview table of parsed OPML entries with columns: feed URL, title, and “Already Imported” status.
6
+ - Mark duplicate entries (feed URL matched against existing sourcemon_sources) as “Already Imported” and make them non-selectable.
7
+ - Show malformed/parsing-error entries (from parsed_sources) with inline error indicator and make them non-selectable.
8
+ - Provide filters: All, New Sources, Existing Sources (server-side filtering).
9
+ - Allow admin to select/unselect valid entries; persist selections across filter changes and step navigation by updating ImportSession.selected_source_ids.
10
+ - Prevent proceeding to the next wizard step when no sources are selected and surface a clear warning.
11
+
12
+ - **Technical Guidelines**
13
+ - Rendering & UI
14
+ - Implement the Preview step view as a Turbo Frame (consistent with engine conventions used for sources index tables). The table content must be server-rendered (HTML partials) and replaceable via turbo_frame_tag updates.
15
+ - Table columns: feed URL (clickable/visible text), title (as parsed), and “Already Imported” status (visual badge/icon). Malformed rows must show a clear inline error message/toolip and a disabled checkbox.
16
+ - Use Tailwind classes and accessibility patterns consistent with other engine tables and forms.
17
+ - Add filter controls (All / New / Existing) that trigger full-frame GET requests (targeting the preview Turbo Frame) and support pagination & stateful checkbox persistence.
18
+ - Data & Selection Persistence
19
+ - Persist and read selection state in ImportSession.selected_source_ids (JSONB). Store a stable identifier for each parsed entry that can be used across requests. If parsed_sources includes a unique temp id, use it; otherwise use feed_url as the canonical identifier for selection storage (feed_url is required for duplicate detection).
20
+ - When rendering rows, mark checkboxes checked if their identifier appears in ImportSession.selected_source_ids.
21
+ - Update ImportSession.selected_source_ids via server endpoints (e.g., POST/PATCH from the preview frame) whenever a user toggles selection or submits the preview form. The controller actions must sanitize params and scope updates to the current ImportSession and current admin user.
22
+ - Ensure ImportSession model uses integer user_id as configured in the tech brief. (This task reads/updates ImportSession only; ensure permission checks enforce admin-only access.)
23
+ - Duplicate & Malformed Handling
24
+ - Duplicate detection: treat a parsed entry as an "existing" duplicate if its feed URL exactly matches an existing source record (sourcemon_sources). Mark duplicates visually and make their selection control disabled.
25
+ - Malformed entries: rely on parser-set flags in ImportSession.parsed_sources to identify malformed entries; render error details inline and disable selection.
26
+ - Filters & Pagination
27
+ - Implement server-side filtering logic:
28
+ - All => show all parsed_sources (including malformed and duplicates).
29
+ - New Sources => parsed entries that are valid and not duplicate.
30
+ - Existing Sources => parsed entries flagged as duplicate (already imported).
31
+ - Implement server-side pagination for the preview table using existing pagination helpers/patterns used elsewhere in the engine (do not load entire list into browser for very large files).
32
+ - Preserve selection state across pagination and filter changes by reading/writing ImportSession.selected_source_ids (i.e., selections are global to the ImportSession, not only per page).
33
+ - Navigation & Progression Block
34
+ - Provide UI validation that blocks “Next” navigation when ImportSession.selected_source_ids is empty (or contains no enabled/valid entries). Show a clear, inline warning message in the Preview step explaining at least one valid source must be selected.
35
+ - Security & Conventions
36
+ - Enforce admin-only access for all preview endpoints using existing SourceMonitor authentication hooks.
37
+ - Use strong parameter sanitization for any incoming params (filter, page, selection updates).
38
+ - Follow the engine’s Turbo/Tailwind/Accessibility patterns and Turbo Frame naming conventions so later steps and Turbo Streams integrate cleanly.
39
+ - Integration points (do not reimplement)
40
+ - Read parsed entries from ImportSession.parsed_sources (populated by the Upload/Parsing step).
41
+ - For duplicate detection, query the canonical Source model/table (sourcemon_sources / SourceMonitor::Source) — match by feed URL only.
42
+ - Use existing pagination helpers and table rendering conventions used by the Sources index.
43
+
44
+ - **Out of scope**
45
+ - Health check job orchestration, Turbo Stream live broadcasting for health — that is handled in the Health Check step.
46
+ - Upload/parsing implementation (Add OPML Upload & Synchronous Parsing) and ImportSession creation — assume ImportSession.parsed_sources exists with required fields.
47
+ - Per-feed settings UI or per-feed overrides.
48
+ - Client-only selection persistence (all persistence must be server-backed in ImportSession.selected_source_ids).
49
+ - Retry semantics for failed persistence or selection race-handling beyond simple idempotent updates and param sanitization.
50
+
51
+ - **Suggested research**
52
+ - Inspect ImportSession model and its parsed_sources schema to confirm the entry shape and any temporary identifiers (or confirm to use feed_url as identifier).
53
+ - Review the wizard controller/routes for Preview step (ImportSession/wizard controller) to determine the Turbo Frame name and route endpoints for updating selection and paginated frame rendering.
54
+ - Review existing sources index/table implementation and pagination helpers to follow consistent Turbo Frame patterns and CSS classes:
55
+ - app/views/source_monitor/sources/index.html.erb and its table partials
56
+ - existing pagination helpers under lib/source_monitor/pagination or helpers used in sources index
57
+ - Inspect Source model / table for feed_url column and any scopes used for matching (sourcemon_sources / SourceMonitor::Source).
58
+ - Check authentication hooks and parameter sanitizer patterns used by controllers to ensure admin-only access and strong param handling (e.g., SourceMonitor.config.authentication, SourceMonitor::Security::ParameterSanitizer).
@@ -0,0 +1,18 @@
1
+ #### Goals
2
+
3
+ - Render a Preview step showing a paginated table of parsed sources with columns: feed URL, title, and "Already Imported" status.
4
+ - Detect duplicates by matching feed URL against existing sourcemon_sources and mark duplicates as non-selectable.
5
+ - Provide filters to toggle between "All", "New Sources", and "Existing Sources" and ensure selections persist across filter changes and step navigation.
6
+ - Prevent progression to the next step if all sources are deselected and show a clear warning.
7
+
8
+ #### Technical Considerations
9
+
10
+ - Implement the preview UI using Turbo Frames for the table and follow existing sources index patterns (search form targeting a turbo frame, Tailwind styling and accessibility conventions).
11
+ - Store user selection state in ImportSession.selected_source_ids (JSONB) so selections persist across wizard steps and page navigations while the ImportSession exists. Ensure the ImportSession model uses an integer-based user reference (standard Rails user_id integer foreign key).
12
+ - Duplicate detection uses only feed URL matching (as decided); malformed entries flagged by parsing step must be disabled for selection.
13
+ - Implement server-side pagination for large parsed lists using existing pagination helpers used in the engine.
14
+
15
+ #### Dependencies
16
+
17
+ - Add OPML Upload & Synchronous Parsing (parsing fills parsed_sources used to render preview)
18
+ - Implement OPML Import Wizard Shell (routes, ImportSession persistence and wizard navigation)