source_monitor 0.7.0 → 0.8.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 (141) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/release.md +45 -22
  3. data/.claude/skills/sm-configure/SKILL.md +10 -1
  4. data/.claude/skills/sm-configure/reference/configuration-reference.md +44 -0
  5. data/.claude/skills/sm-host-setup/reference/initializer-template.md +17 -0
  6. data/.claude/skills/sm-host-setup/reference/setup-checklist.md +2 -0
  7. data/.claude/skills/sm-job/reference/job-conventions.md +26 -0
  8. data/.claude/skills/sm-upgrade/reference/version-history.md +22 -0
  9. data/.gitignore +10 -0
  10. data/AGENTS.md +1 -1
  11. data/CHANGELOG.md +56 -0
  12. data/CLAUDE.md +11 -5
  13. data/Gemfile.lock +1 -1
  14. data/README.md +6 -4
  15. data/VERSION +1 -1
  16. data/app/assets/builds/source_monitor/application.css +43 -0
  17. data/app/assets/builds/source_monitor/application.js +127 -0
  18. data/app/assets/builds/source_monitor/application.js.map +3 -3
  19. data/app/assets/javascripts/source_monitor/application.js +2 -0
  20. data/app/assets/javascripts/source_monitor/controllers/notification_container_controller.js +138 -0
  21. data/app/assets/javascripts/source_monitor/controllers/notification_controller.js +11 -0
  22. data/app/controllers/source_monitor/source_favicon_fetches_controller.rb +38 -0
  23. data/app/controllers/source_monitor/sources_controller.rb +11 -0
  24. data/app/helpers/source_monitor/application_helper.rb +51 -0
  25. data/app/jobs/source_monitor/favicon_fetch_job.rb +71 -0
  26. data/app/jobs/source_monitor/import_opml_job.rb +9 -0
  27. data/app/jobs/source_monitor/source_health_check_job.rb +10 -0
  28. data/app/models/source_monitor/source.rb +2 -0
  29. data/app/views/layouts/source_monitor/application.html.erb +23 -2
  30. data/app/views/source_monitor/shared/_toast.html.erb +1 -0
  31. data/app/views/source_monitor/sources/_details.html.erb +34 -5
  32. data/app/views/source_monitor/sources/_row.html.erb +11 -6
  33. data/config/routes.rb +1 -0
  34. data/docs/configuration.md +1 -1
  35. data/docs/upgrade.md +22 -0
  36. data/lib/generators/source_monitor/install/templates/source_monitor.rb.tt +15 -1
  37. data/lib/source_monitor/configuration/favicons_settings.rb +42 -0
  38. data/lib/source_monitor/configuration/http_settings.rb +1 -1
  39. data/lib/source_monitor/configuration/scraping_settings.rb +1 -1
  40. data/lib/source_monitor/configuration.rb +3 -1
  41. data/lib/source_monitor/favicons/discoverer.rb +196 -0
  42. data/lib/source_monitor/fetching/feed_fetcher/source_updater.rb +21 -0
  43. data/lib/source_monitor/fetching/feed_fetcher.rb +1 -0
  44. data/lib/source_monitor/http.rb +5 -3
  45. data/lib/source_monitor/version.rb +1 -1
  46. data/lib/source_monitor.rb +4 -0
  47. data/lib/tasks/test_fast.rake +11 -0
  48. data/source_monitor.gemspec +1 -1
  49. metadata +7 -93
  50. data/.vbw-planning/PROJECT.md +0 -51
  51. data/.vbw-planning/ROADMAP.md +0 -32
  52. data/.vbw-planning/SHIPPED.md +0 -63
  53. data/.vbw-planning/STATE.md +0 -27
  54. data/.vbw-planning/codebase/ARCHITECTURE.md +0 -147
  55. data/.vbw-planning/codebase/CONCERNS.md +0 -99
  56. data/.vbw-planning/codebase/CONVENTIONS.md +0 -97
  57. data/.vbw-planning/codebase/DEPENDENCIES.md +0 -100
  58. data/.vbw-planning/codebase/INDEX.md +0 -86
  59. data/.vbw-planning/codebase/META.md +0 -42
  60. data/.vbw-planning/codebase/PATTERNS.md +0 -262
  61. data/.vbw-planning/codebase/STACK.md +0 -101
  62. data/.vbw-planning/codebase/STRUCTURE.md +0 -324
  63. data/.vbw-planning/codebase/TESTING.md +0 -154
  64. data/.vbw-planning/config.json +0 -53
  65. data/.vbw-planning/discovery.json +0 -26
  66. data/.vbw-planning/milestones/default/ROADMAP.md +0 -115
  67. data/.vbw-planning/milestones/default/STATE.md +0 -82
  68. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-01-SUMMARY.md +0 -56
  69. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-01.md +0 -187
  70. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-02-SUMMARY.md +0 -64
  71. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-02.md +0 -137
  72. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-01-SUMMARY.md +0 -67
  73. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-01.md +0 -142
  74. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-02-SUMMARY.md +0 -64
  75. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-02.md +0 -138
  76. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-03-SUMMARY.md +0 -85
  77. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-03.md +0 -147
  78. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-04-SUMMARY.md +0 -63
  79. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-04.md +0 -129
  80. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-05-SUMMARY.md +0 -74
  81. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-05.md +0 -154
  82. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/03-VERIFICATION-wave1.md +0 -303
  83. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/03-VERIFICATION.md +0 -510
  84. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-01-SUMMARY.md +0 -61
  85. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-01.md +0 -161
  86. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-02-SUMMARY.md +0 -66
  87. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-02.md +0 -132
  88. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-03-SUMMARY.md +0 -59
  89. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-03.md +0 -171
  90. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-04-SUMMARY.md +0 -56
  91. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-04.md +0 -152
  92. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/04-CONTEXT.md +0 -33
  93. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-01-SUMMARY.md +0 -42
  94. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-01.md +0 -119
  95. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-02-SUMMARY.md +0 -52
  96. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-02.md +0 -195
  97. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-03-SUMMARY.md +0 -79
  98. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-03.md +0 -130
  99. data/.vbw-planning/milestones/generator-enhancements/REQUIREMENTS.md +0 -72
  100. data/.vbw-planning/milestones/generator-enhancements/ROADMAP.md +0 -125
  101. data/.vbw-planning/milestones/generator-enhancements/SHIPPED.md +0 -40
  102. data/.vbw-planning/milestones/generator-enhancements/STATE.md +0 -43
  103. data/.vbw-planning/milestones/generator-enhancements/phases/01-generator-steps/01-CONTEXT.md +0 -33
  104. data/.vbw-planning/milestones/generator-enhancements/phases/01-generator-steps/01-VERIFICATION.md +0 -86
  105. data/.vbw-planning/milestones/generator-enhancements/phases/01-generator-steps/PLAN-01-SUMMARY.md +0 -61
  106. data/.vbw-planning/milestones/generator-enhancements/phases/01-generator-steps/PLAN-01.md +0 -380
  107. data/.vbw-planning/milestones/generator-enhancements/phases/02-verification/02-VERIFICATION.md +0 -78
  108. data/.vbw-planning/milestones/generator-enhancements/phases/02-verification/PLAN-01-SUMMARY.md +0 -46
  109. data/.vbw-planning/milestones/generator-enhancements/phases/02-verification/PLAN-01.md +0 -500
  110. data/.vbw-planning/milestones/generator-enhancements/phases/03-docs-alignment/03-VERIFICATION.md +0 -89
  111. data/.vbw-planning/milestones/generator-enhancements/phases/03-docs-alignment/PLAN-01-SUMMARY.md +0 -48
  112. data/.vbw-planning/milestones/generator-enhancements/phases/03-docs-alignment/PLAN-01.md +0 -456
  113. data/.vbw-planning/milestones/generator-enhancements/phases/04-dashboard-ux/04-VERIFICATION.md +0 -129
  114. data/.vbw-planning/milestones/generator-enhancements/phases/04-dashboard-ux/PLAN-01-SUMMARY.md +0 -70
  115. data/.vbw-planning/milestones/generator-enhancements/phases/04-dashboard-ux/PLAN-01.md +0 -747
  116. data/.vbw-planning/milestones/generator-enhancements/phases/05-active-storage-images/05-VERIFICATION.md +0 -156
  117. data/.vbw-planning/milestones/generator-enhancements/phases/05-active-storage-images/PLAN-01-SUMMARY.md +0 -69
  118. data/.vbw-planning/milestones/generator-enhancements/phases/05-active-storage-images/PLAN-01.md +0 -455
  119. data/.vbw-planning/milestones/generator-enhancements/phases/05-active-storage-images/PLAN-02-SUMMARY.md +0 -39
  120. data/.vbw-planning/milestones/generator-enhancements/phases/05-active-storage-images/PLAN-02.md +0 -488
  121. data/.vbw-planning/milestones/generator-enhancements/phases/06-netflix-feed-fix/06-VERIFICATION.md +0 -100
  122. data/.vbw-planning/milestones/generator-enhancements/phases/06-netflix-feed-fix/PLAN-01-SUMMARY.md +0 -37
  123. data/.vbw-planning/milestones/generator-enhancements/phases/06-netflix-feed-fix/PLAN-01.md +0 -345
  124. data/.vbw-planning/milestones/upgrade-assurance/REQUIREMENTS.md +0 -80
  125. data/.vbw-planning/milestones/upgrade-assurance/ROADMAP.md +0 -75
  126. data/.vbw-planning/milestones/upgrade-assurance/STATE.md +0 -29
  127. data/.vbw-planning/milestones/upgrade-assurance/phases/01-upgrade-command/01-VERIFICATION.md +0 -144
  128. data/.vbw-planning/milestones/upgrade-assurance/phases/01-upgrade-command/PLAN-01-SUMMARY.md +0 -43
  129. data/.vbw-planning/milestones/upgrade-assurance/phases/01-upgrade-command/PLAN-01.md +0 -405
  130. data/.vbw-planning/milestones/upgrade-assurance/phases/02-config-deprecation/PLAN-01-SUMMARY.md +0 -27
  131. data/.vbw-planning/milestones/upgrade-assurance/phases/02-config-deprecation/PLAN-01.md +0 -303
  132. data/.vbw-planning/milestones/upgrade-assurance/phases/03-upgrade-skill-docs/03-VERIFICATION.md +0 -380
  133. data/.vbw-planning/milestones/upgrade-assurance/phases/03-upgrade-skill-docs/PLAN-01-SUMMARY.md +0 -36
  134. data/.vbw-planning/milestones/upgrade-assurance/phases/03-upgrade-skill-docs/PLAN-01.md +0 -652
  135. data/.vbw-planning/phases/01-aia-certificate-resolution/.context-dev.md +0 -17
  136. data/.vbw-planning/phases/01-aia-certificate-resolution/PLAN-01-SUMMARY.md +0 -26
  137. data/.vbw-planning/phases/01-aia-certificate-resolution/PLAN-01.md +0 -71
  138. data/.vbw-planning/phases/01-aia-certificate-resolution/PLAN-02-SUMMARY.md +0 -16
  139. data/.vbw-planning/phases/01-aia-certificate-resolution/PLAN-02.md +0 -56
  140. data/.vbw-planning/phases/01-aia-certificate-resolution/PLAN-03-SUMMARY.md +0 -17
  141. data/.vbw-planning/phases/01-aia-certificate-resolution/PLAN-03.md +0 -98
@@ -1,161 +0,0 @@
1
- ---
2
- phase: 3
3
- plan: 1
4
- title: extract-feed-fetcher
5
- wave: 1
6
- depends_on: []
7
- skills_used: []
8
- must_haves:
9
- truths:
10
- - "Running `wc -l lib/source_monitor/fetching/feed_fetcher.rb` shows fewer than 300 lines"
11
- - "Running `bin/rails test test/lib/source_monitor/fetching/feed_fetcher_test.rb` exits 0 with zero failures"
12
- - "Running `bin/rails test` exits 0 with no regressions (760+ runs, 0 failures)"
13
- - "Running `grep -r 'FeedFetcher' test/ --include='*.rb' -l` shows no test files were renamed or removed"
14
- - "Running `ruby -c lib/source_monitor/fetching/feed_fetcher.rb` exits 0 (valid syntax)"
15
- - "Running `ruby -c lib/source_monitor/fetching/feed_fetcher/source_updater.rb` exits 0"
16
- - "Running `ruby -c lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb` exits 0"
17
- - "Running `ruby -c lib/source_monitor/fetching/feed_fetcher/entry_processor.rb` exits 0"
18
- artifacts:
19
- - "lib/source_monitor/fetching/feed_fetcher/source_updater.rb -- extracted source state update logic"
20
- - "lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb -- extracted adaptive interval computation"
21
- - "lib/source_monitor/fetching/feed_fetcher/entry_processor.rb -- extracted feed entry processing"
22
- - "lib/source_monitor/fetching/feed_fetcher.rb -- slimmed to orchestrator under 300 lines"
23
- key_links:
24
- - "REQ-08 satisfied -- FeedFetcher broken into focused single-responsibility modules"
25
- - "Public API unchanged -- FeedFetcher.new(source:).call still returns Result struct"
26
- ---
27
-
28
- # Plan 01: extract-feed-fetcher
29
-
30
- ## Objective
31
-
32
- Extract `lib/source_monitor/fetching/feed_fetcher.rb` (627 lines) into focused sub-modules following the existing extraction pattern used by `item_scraper/` (which already has `adapter_resolver.rb` and `persistence.rb` sub-modules) and `completion/` (which has `event_publisher.rb`, `follow_up_handler.rb`, `retention_handler.rb`). The public API (`FeedFetcher.new(source:).call` returning a `Result` struct) must remain unchanged. All 1219 lines of existing tests in `feed_fetcher_test.rb` must continue to pass without modification.
33
-
34
- ## Context
35
-
36
- <context>
37
- @lib/source_monitor/fetching/feed_fetcher.rb -- 627 lines, the core fetch pipeline. Contains HTTP request handling, feed parsing, entry processing, source state updates (success/not_modified/failure), adaptive interval computation, retry strategy application, fetch logging, error wrapping, and various utility helpers.
38
- @lib/source_monitor/fetching/retry_policy.rb -- 85 lines, already extracted. RetryPolicy with Decision struct.
39
- @lib/source_monitor/fetching/fetch_error.rb -- 88 lines, already extracted. Error hierarchy.
40
- @lib/source_monitor/fetching/completion/ -- existing extraction pattern: event_publisher.rb (22 lines), follow_up_handler.rb (37 lines), retention_handler.rb (30 lines). These are loaded via require from fetch_runner.rb.
41
- @lib/source_monitor/scraping/item_scraper.rb -- another extraction example: main class requires item_scraper/adapter_resolver.rb and item_scraper/persistence.rb
42
- @test/lib/source_monitor/fetching/feed_fetcher_test.rb -- 1219 lines, 48+ tests covering all branches. MUST NOT be modified.
43
- @lib/source_monitor.rb -- has `require "source_monitor/fetching/feed_fetcher"` on line 79. New sub-files will be required from feed_fetcher.rb itself (matching item_scraper pattern).
44
-
45
- **Decomposition rationale:** FeedFetcher has three clearly separable responsibility clusters: (1) source state updates after fetch (update_source_for_success, update_source_for_not_modified, update_source_for_failure, reset_retry_state!, apply_retry_strategy!, create_fetch_log -- ~120 lines), (2) adaptive interval computation (apply_adaptive_interval!, compute_next_interval_seconds, adjusted_interval_with_jitter, jitter_offset, and all interval config helpers -- ~110 lines), (3) entry processing (process_feed_entries, normalize_item_error, safe_entry_guid, safe_entry_title -- ~80 lines). The remaining orchestration (call, perform_fetch, handle_response, handle_success, handle_not_modified, handle_failure, HTTP helpers) stays in the main file.
46
-
47
- **Trade-offs considered:**
48
- - Could extract HTTP/connection as a fourth module, but perform_request and connection are only ~10 lines and tightly coupled to the orchestrator.
49
- - Could use Ruby mixins (include/extend) instead of delegation, but delegation preserves clear ownership and matches the item_scraper pattern.
50
- - Structs (Result, EntryProcessingResult, ResponseWrapper) stay in the main file because they define the public API contract.
51
-
52
- **What constrains the structure:**
53
- - The main file must require its sub-modules (matching item_scraper pattern)
54
- - Sub-modules need access to source, fetching_config, and other state -- pass via constructor or delegate
55
- - All tests pass without modification -- the public API is preserved
56
- - Each extracted module lives in lib/source_monitor/fetching/feed_fetcher/ directory (matching item_scraper/ pattern)
57
- </context>
58
-
59
- ## Tasks
60
-
61
- **Execution note:** Task 2 (AdaptiveInterval) should be completed before Task 1 (SourceUpdater) because SourceUpdater depends on AdaptiveInterval for the `apply_adaptive_interval!` method. Task 3 (EntryProcessor) is independent of the other two. Task 4 is the final wiring pass.
62
-
63
- ### Task 1: Extract SourceUpdater module
64
-
65
- - **name:** extract-source-updater
66
- - **files:**
67
- - `lib/source_monitor/fetching/feed_fetcher/source_updater.rb` (new)
68
- - `lib/source_monitor/fetching/feed_fetcher.rb`
69
- - **action:** Create `lib/source_monitor/fetching/feed_fetcher/source_updater.rb` containing a `SourceMonitor::Fetching::FeedFetcher::SourceUpdater` class. Move these methods from feed_fetcher.rb into the new class:
70
- - `update_source_for_success` (lines 192-216)
71
- - `update_source_for_not_modified` (lines 218-241)
72
- - `update_source_for_failure` (lines 243-259)
73
- - `reset_retry_state!` (lines 261-265)
74
- - `apply_retry_strategy!` (lines 267-299)
75
- - `create_fetch_log` (lines 301-320)
76
- - `feed_metadata` (lines 328-335)
77
- - `normalized_headers` (lines 337-341)
78
- - `error_backtrace` (lines 343-347)
79
- - `derive_feed_format` (lines 322-326)
80
- - `feed_signature_changed?` (lines 419-423)
81
- - `updated_metadata` (lines 490-495)
82
- - `parse_http_time` (lines 349-355)
83
- - `elapsed_ms` (lines 357-359)
84
-
85
- The SourceUpdater constructor takes `source:` and `adaptive_interval:` (the AdaptiveInterval instance from Task 2). Add `require "source_monitor/fetching/feed_fetcher/source_updater"` at the top of feed_fetcher.rb. In FeedFetcher, create a `source_updater` method that lazily instantiates the SourceUpdater passing source and adaptive_interval. Replace all calls to the moved methods with delegation to `source_updater.method_name`. The SourceUpdater must be a private implementation detail -- not exposed in the public API.
86
- - **verify:** `ruby -c lib/source_monitor/fetching/feed_fetcher/source_updater.rb` exits 0 AND `bin/rails test test/lib/source_monitor/fetching/feed_fetcher_test.rb` exits 0 with zero failures
87
- - **done:** SourceUpdater extracted. All calls delegated. Tests pass unchanged.
88
-
89
- ### Task 2: Extract AdaptiveInterval module
90
-
91
- - **name:** extract-adaptive-interval
92
- - **files:**
93
- - `lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb` (new)
94
- - `lib/source_monitor/fetching/feed_fetcher.rb`
95
- - **action:** Create `lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb` containing a `SourceMonitor::Fetching::FeedFetcher::AdaptiveInterval` class. Move these methods:
96
- - `apply_adaptive_interval!` (lines 425-438)
97
- - `compute_next_interval_seconds` (lines 441-455)
98
- - `current_interval_seconds` (lines 457-459)
99
- - `interval_minutes_for` (lines 461-464)
100
- - `min_fetch_interval_seconds` (lines 466-468)
101
- - `max_fetch_interval_seconds` (lines 470-472)
102
- - `increase_factor_value` (lines 474-476)
103
- - `decrease_factor_value` (lines 478-480)
104
- - `failure_increase_factor_value` (lines 482-484)
105
- - `jitter_percent_value` (lines 486-488)
106
- - `adjusted_interval_with_jitter` (lines 497-502)
107
- - `jitter_offset` (lines 504-512)
108
- - `configured_seconds` (lines 569-574)
109
- - `configured_positive` (lines 576-580)
110
- - `configured_non_negative` (lines 583-588)
111
- - `extract_numeric` (lines 590-597)
112
- - `fetching_config` (lines 599-601)
113
-
114
- Also move the constants: `MIN_FETCH_INTERVAL`, `MAX_FETCH_INTERVAL`, `INCREASE_FACTOR`, `DECREASE_FACTOR`, `FAILURE_INCREASE_FACTOR`, `JITTER_PERCENT`.
115
-
116
- The constructor takes `source:` and `jitter_proc:`. Add `require "source_monitor/fetching/feed_fetcher/adaptive_interval"` at the top of feed_fetcher.rb. In FeedFetcher, create an `adaptive_interval` method that lazily instantiates AdaptiveInterval. Replace all calls to moved methods with delegation. Keep the constant references working by aliasing from the main class or referencing the sub-module.
117
- - **verify:** `ruby -c lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb` exits 0 AND `bin/rails test test/lib/source_monitor/fetching/feed_fetcher_test.rb` exits 0
118
- - **done:** AdaptiveInterval extracted. Constants accessible. Tests pass unchanged.
119
-
120
- ### Task 3: Extract EntryProcessor module
121
-
122
- - **name:** extract-entry-processor
123
- - **files:**
124
- - `lib/source_monitor/fetching/feed_fetcher/entry_processor.rb` (new)
125
- - `lib/source_monitor/fetching/feed_fetcher.rb`
126
- - **action:** Create `lib/source_monitor/fetching/feed_fetcher/entry_processor.rb` containing a `SourceMonitor::Fetching::FeedFetcher::EntryProcessor` class. Move these methods:
127
- - `process_feed_entries` (lines 520-567)
128
- - `normalize_item_error` (lines 603-612)
129
- - `safe_entry_guid` (lines 614-620)
130
- - `safe_entry_title` (lines 622-624)
131
-
132
- The constructor takes `source:`. It returns `EntryProcessingResult` structs (which stay defined in the main FeedFetcher class and are referenced as `FeedFetcher::EntryProcessingResult`). Add `require "source_monitor/fetching/feed_fetcher/entry_processor"` at the top of feed_fetcher.rb. In FeedFetcher, create an `entry_processor` method and delegate `process_feed_entries` to it. The other methods are only called from within entry_processor so they move wholesale.
133
- - **verify:** `ruby -c lib/source_monitor/fetching/feed_fetcher/entry_processor.rb` exits 0 AND `bin/rails test test/lib/source_monitor/fetching/feed_fetcher_test.rb` exits 0
134
- - **done:** EntryProcessor extracted. Tests pass unchanged.
135
-
136
- ### Task 4: Wire extracted modules and verify final line count
137
-
138
- - **name:** wire-modules-and-verify
139
- - **files:**
140
- - `lib/source_monitor/fetching/feed_fetcher.rb`
141
- - **action:** After Tasks 1-3, the main feed_fetcher.rb should contain: require statements for the 3 sub-modules, the Struct definitions (Result, EntryProcessingResult, ResponseWrapper), the constructor, `call`, `perform_fetch`, `handle_response`, `handle_success`, `handle_not_modified`, `handle_failure`, `perform_request`, `connection`, `request_headers`, `build_http_error_from_faraday`, `body_digest`, and the lazy accessor methods for source_updater, adaptive_interval, and entry_processor. Clean up any dead code, unused private methods, or orphaned requires. Ensure no method is duplicated between the main file and sub-modules. Verify the main file is under 300 lines. Run the full test suite to confirm no regressions.
142
- - **verify:** `wc -l lib/source_monitor/fetching/feed_fetcher.rb` shows fewer than 300 lines AND `bin/rails test` exits 0 with 760+ runs and 0 failures AND `bin/rubocop lib/source_monitor/fetching/feed_fetcher.rb lib/source_monitor/fetching/feed_fetcher/` exits 0
143
- - **done:** FeedFetcher under 300 lines. All sub-modules syntactically valid. Full suite passes. RuboCop clean.
144
-
145
- ## Verification
146
-
147
- 1. `wc -l lib/source_monitor/fetching/feed_fetcher.rb` shows fewer than 300 lines
148
- 2. `wc -l lib/source_monitor/fetching/feed_fetcher/source_updater.rb lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb lib/source_monitor/fetching/feed_fetcher/entry_processor.rb` shows all exist
149
- 3. `bin/rails test test/lib/source_monitor/fetching/feed_fetcher_test.rb` exits 0 with zero failures
150
- 4. `bin/rails test` exits 0 (no regressions)
151
- 5. `bin/rubocop lib/source_monitor/fetching/` exits 0
152
-
153
- ## Success Criteria
154
-
155
- - [ ] FeedFetcher main file under 300 lines
156
- - [ ] Three sub-modules created: source_updater.rb, adaptive_interval.rb, entry_processor.rb
157
- - [ ] Public API unchanged -- FeedFetcher.new(source:).call returns Result struct
158
- - [ ] All existing tests pass without modification (1219 lines, 48+ tests)
159
- - [ ] Full test suite passes (760+ runs, 0 failures)
160
- - [ ] RuboCop passes on all modified/new files
161
- - [ ] REQ-08 satisfied
@@ -1,66 +0,0 @@
1
- # PLAN-02 Summary: extract-configuration-settings
2
-
3
- ## Status: COMPLETE
4
-
5
- ## Commits
6
-
7
- - **Hash:** `ab823a3`
8
- - **Message:** `refactor(configuration): extract 12 nested classes into separate files [PLAN-02]`
9
- - **Files changed:** 13 files, 653 insertions, 579 deletions
10
-
11
- ## Tasks Completed
12
-
13
- ### Task 1: Extract basic settings (HTTP, Fetching, Health, Scraping)
14
- - Created `lib/source_monitor/configuration/http_settings.rb` (43 lines)
15
- - Created `lib/source_monitor/configuration/fetching_settings.rb` (27 lines)
16
- - Created `lib/source_monitor/configuration/health_settings.rb` (27 lines)
17
- - Created `lib/source_monitor/configuration/scraping_settings.rb` (39 lines)
18
- - Removed class bodies from configuration.rb, added require statements
19
- - All 81 configuration tests pass
20
-
21
- ### Task 2: Extract complex settings (Realtime, Retention, Authentication)
22
- - Created `lib/source_monitor/configuration/realtime_settings.rb` (95 lines, includes SolidCableOptions)
23
- - Created `lib/source_monitor/configuration/retention_settings.rb` (45 lines)
24
- - Created `lib/source_monitor/configuration/authentication_settings.rb` (62 lines, includes Handler struct)
25
- - All 81 configuration tests pass
26
-
27
- ### Task 3: Extract registry, events, models, and definition classes
28
- - Created `lib/source_monitor/configuration/scraper_registry.rb` (67 lines)
29
- - Created `lib/source_monitor/configuration/events.rb` (60 lines)
30
- - Created `lib/source_monitor/configuration/models.rb` (36 lines)
31
- - Created `lib/source_monitor/configuration/model_definition.rb` (108 lines, includes ConcernDefinition)
32
- - Created `lib/source_monitor/configuration/validation_definition.rb` (32 lines)
33
- - Configuration.rb reduced to 87 lines
34
- - All 81 configuration tests pass
35
-
36
- ### Task 4: Verify line counts, RuboCop, and full test suite
37
- - Configuration.rb: 87 lines (target: under 120)
38
- - 12 extracted files, none exceeds 300 lines (largest: model_definition.rb at 108)
39
- - RuboCop: 13 files inspected, 0 offenses
40
- - Full suite: 760 runs, 28 errors (all from concurrent dev work on FeedFetcher/ImportSessions extraction, not from this plan)
41
-
42
- ## Deviations
43
-
44
- | ID | Description | Impact |
45
- |----|-------------|--------|
46
- | None | No deviations from plan | N/A |
47
-
48
- ## Verification Results
49
-
50
- | Check | Result |
51
- |-------|--------|
52
- | `wc -l lib/source_monitor/configuration.rb` | 87 lines (target: <120) |
53
- | `ls lib/source_monitor/configuration/*.rb \| wc -l` | 12 files |
54
- | `grep -c 'class.*Settings\|class.*Registry\|class.*Events\|class.*Models\|class.*Definition' lib/source_monitor/configuration.rb` | 0 (all nested classes extracted) |
55
- | `bin/rails test test/lib/source_monitor/configuration_test.rb` | 81 runs, 178 assertions, 0 failures, 0 errors |
56
- | `bin/rubocop lib/source_monitor/configuration.rb lib/source_monitor/configuration/` | 13 files inspected, 0 offenses |
57
-
58
- ## Success Criteria
59
-
60
- - [x] Configuration main file under 120 lines (87, down from 655)
61
- - [x] 12 extracted files in lib/source_monitor/configuration/
62
- - [x] No extracted file exceeds 300 lines (largest: 108)
63
- - [x] Public API unchanged -- SourceMonitor.configure { |c| c.http.timeout = 30 } works
64
- - [x] All existing configuration tests pass without modification (81 runs, 0 failures)
65
- - [x] RuboCop passes on all modified/new files
66
- - [x] REQ-09 satisfied
@@ -1,132 +0,0 @@
1
- ---
2
- phase: 3
3
- plan: 2
4
- title: extract-configuration-settings
5
- wave: 1
6
- depends_on: []
7
- skills_used: []
8
- must_haves:
9
- truths:
10
- - "Running `wc -l lib/source_monitor/configuration.rb` shows fewer than 120 lines"
11
- - "Running `bin/rails test test/lib/source_monitor/configuration_test.rb` exits 0 with zero failures"
12
- - "Running `bin/rails test` exits 0 with no regressions (760+ runs, 0 failures)"
13
- - "Running `ls lib/source_monitor/configuration/` shows at least 10 .rb files"
14
- - "Running `ruby -c lib/source_monitor/configuration.rb` exits 0"
15
- - "Running `grep -c 'class.*Settings\\|class.*Registry\\|class.*Events\\|class.*Models\\|class.*Definition' lib/source_monitor/configuration.rb` shows 0 (all nested classes extracted)"
16
- artifacts:
17
- - "lib/source_monitor/configuration/http_settings.rb"
18
- - "lib/source_monitor/configuration/fetching_settings.rb"
19
- - "lib/source_monitor/configuration/health_settings.rb"
20
- - "lib/source_monitor/configuration/realtime_settings.rb (includes SolidCableOptions)"
21
- - "lib/source_monitor/configuration/scraping_settings.rb"
22
- - "lib/source_monitor/configuration/retention_settings.rb"
23
- - "lib/source_monitor/configuration/scraper_registry.rb"
24
- - "lib/source_monitor/configuration/events.rb"
25
- - "lib/source_monitor/configuration/models.rb"
26
- - "lib/source_monitor/configuration/model_definition.rb (includes ConcernDefinition)"
27
- - "lib/source_monitor/configuration/validation_definition.rb"
28
- - "lib/source_monitor/configuration/authentication_settings.rb (includes Handler)"
29
- - "lib/source_monitor/configuration.rb -- slimmed to container class under 120 lines"
30
- key_links:
31
- - "REQ-09 satisfied -- Configuration nested classes extracted into separate files"
32
- - "Public API unchanged -- SourceMonitor.configure { |c| c.http.timeout = 30 } still works"
33
- ---
34
-
35
- # Plan 02: extract-configuration-settings
36
-
37
- ## Objective
38
-
39
- Extract the 12 nested classes from `lib/source_monitor/configuration.rb` (655 lines) into individual files under `lib/source_monitor/configuration/`. Each nested class (HTTPSettings, FetchingSettings, HealthSettings, RealtimeSettings + SolidCableOptions, ScrapingSettings, RetentionSettings, ScraperRegistry, Events, Models, ModelDefinition + ConcernDefinition, ValidationDefinition, AuthenticationSettings + Handler) becomes its own file. The main `configuration.rb` retains only the `Configuration` class shell with constructor, `queue_name_for`, `concurrency_for`, attr_accessors, and attr_readers. The public API (`SourceMonitor.configure { |c| ... }`) remains unchanged.
40
-
41
- ## Context
42
-
43
- <context>
44
- @lib/source_monitor/configuration.rb -- 655 lines. Contains Configuration class with ~70 lines of its own logic, plus 12 nested classes totaling ~585 lines. Each nested class is already well-encapsulated with its own initialize, reset!, and domain-specific methods.
45
- @test/lib/source_monitor/configuration_test.rb -- 860 lines of tests covering all settings classes. MUST NOT be modified.
46
- @lib/source_monitor.rb -- line 41: `require "source_monitor/configuration"`. New sub-files will be required from configuration.rb itself.
47
-
48
- **Decomposition rationale:** The Configuration file is large solely because it contains 12 independently testable classes. Unlike FeedFetcher (which requires careful method delegation), this extraction is mechanical: move each class to its own file, add require statements, and verify. The classes have no circular dependencies between them -- they're all leaf nodes of the Configuration tree.
49
-
50
- **Trade-offs considered:**
51
- - Could keep small classes (like FetchingSettings at 20 lines) inline and only extract large ones. But consistency is more valuable than saving a few files, and the target is under 300 lines total.
52
- - Could use autoload instead of require. Deferring to Plan 04 (REQ-12) which handles autoloading holistically.
53
- - SolidCableOptions is nested inside RealtimeSettings. Keep them in the same file (`realtime_settings.rb`) since SolidCableOptions is only used by RealtimeSettings.
54
- - ConcernDefinition is nested inside ModelDefinition. Keep them in the same file (`model_definition.rb`).
55
- - Handler is nested inside AuthenticationSettings. Keep them in the same file (`authentication_settings.rb`).
56
-
57
- **What constrains the structure:**
58
- - Nested class names must remain accessible as `Configuration::HTTPSettings`, `Configuration::Events`, etc.
59
- - All requires go in configuration.rb before the class body
60
- - Tests reference these classes via `SourceMonitor::Configuration::EventsClass` or through `SourceMonitor.config.events` -- both patterns must continue working
61
- - `frozen_string_literal: true` pragma on all new files
62
- </context>
63
-
64
- ## Tasks
65
-
66
- ### Task 1: Extract settings classes (HTTP, Fetching, Health, Scraping)
67
-
68
- - **name:** extract-basic-settings
69
- - **files:**
70
- - `lib/source_monitor/configuration/http_settings.rb` (new)
71
- - `lib/source_monitor/configuration/fetching_settings.rb` (new)
72
- - `lib/source_monitor/configuration/health_settings.rb` (new)
73
- - `lib/source_monitor/configuration/scraping_settings.rb` (new)
74
- - `lib/source_monitor/configuration.rb`
75
- - **action:** Create `lib/source_monitor/configuration/` directory. For each of the four settings classes (HTTPSettings lines 256-292, FetchingSettings lines 294-314, HealthSettings lines 316-336, ScrapingSettings lines 132-164), create a new file with the class defined as `SourceMonitor::Configuration::ClassName`. Each file gets `frozen_string_literal: true`. Add require statements at the top of configuration.rb: `require "source_monitor/configuration/http_settings"` etc. Remove the class bodies from configuration.rb. Verify the class is still accessible as `SourceMonitor::Configuration::HTTPSettings`.
76
- - **verify:** `ruby -c lib/source_monitor/configuration/http_settings.rb lib/source_monitor/configuration/fetching_settings.rb lib/source_monitor/configuration/health_settings.rb lib/source_monitor/configuration/scraping_settings.rb` exits 0 AND `bin/rails test test/lib/source_monitor/configuration_test.rb` exits 0
77
- - **done:** Four simple settings classes extracted. Tests pass.
78
-
79
- ### Task 2: Extract complex settings classes (Realtime, Retention, Authentication)
80
-
81
- - **name:** extract-complex-settings
82
- - **files:**
83
- - `lib/source_monitor/configuration/realtime_settings.rb` (new, includes SolidCableOptions)
84
- - `lib/source_monitor/configuration/retention_settings.rb` (new)
85
- - `lib/source_monitor/configuration/authentication_settings.rb` (new, includes Handler struct)
86
- - `lib/source_monitor/configuration.rb`
87
- - **action:** Extract RealtimeSettings (lines 166-254, includes SolidCableOptions inner class), RetentionSettings (lines 398-436), and AuthenticationSettings (lines 75-130, includes Handler Struct). Keep SolidCableOptions nested inside RealtimeSettings in the same file. Keep Handler nested inside AuthenticationSettings in the same file. Add require statements to configuration.rb. Remove class bodies from configuration.rb.
88
- - **verify:** `ruby -c lib/source_monitor/configuration/realtime_settings.rb lib/source_monitor/configuration/retention_settings.rb lib/source_monitor/configuration/authentication_settings.rb` exits 0 AND `bin/rails test test/lib/source_monitor/configuration_test.rb` exits 0
89
- - **done:** Three complex settings classes extracted with their inner classes. Tests pass.
90
-
91
- ### Task 3: Extract registry, events, models, and definition classes
92
-
93
- - **name:** extract-registry-events-models
94
- - **files:**
95
- - `lib/source_monitor/configuration/scraper_registry.rb` (new)
96
- - `lib/source_monitor/configuration/events.rb` (new)
97
- - `lib/source_monitor/configuration/models.rb` (new)
98
- - `lib/source_monitor/configuration/model_definition.rb` (new, includes ConcernDefinition)
99
- - `lib/source_monitor/configuration/validation_definition.rb` (new)
100
- - `lib/source_monitor/configuration.rb`
101
- - **action:** Extract ScraperRegistry (lines 338-396), Events (lines 438-491), Models (lines 493-522), ModelDefinition (lines 524-625, includes ConcernDefinition inner class), and ValidationDefinition (lines 627-652). Keep ConcernDefinition nested inside ModelDefinition. Add require statements to configuration.rb. Remove all remaining nested class bodies from configuration.rb. The main configuration.rb should now contain only: require statements, the Configuration class with attr_accessor/attr_reader declarations, initialize (instantiating all settings objects), queue_name_for, and concurrency_for.
102
- - **verify:** `ruby -c lib/source_monitor/configuration/scraper_registry.rb lib/source_monitor/configuration/events.rb lib/source_monitor/configuration/models.rb lib/source_monitor/configuration/model_definition.rb lib/source_monitor/configuration/validation_definition.rb` exits 0 AND `bin/rails test test/lib/source_monitor/configuration_test.rb` exits 0 AND `wc -l lib/source_monitor/configuration.rb` shows fewer than 120 lines
103
- - **done:** All nested classes extracted. Configuration.rb under 120 lines. Full configuration test suite passes.
104
-
105
- ### Task 4: Verify line counts and full suite
106
-
107
- - **name:** verify-extraction-complete
108
- - **files:**
109
- - `lib/source_monitor/configuration.rb`
110
- - `lib/source_monitor/configuration/*.rb`
111
- - **action:** Run `wc -l` on all extracted files and the main configuration.rb. Verify no extracted file exceeds 300 lines. Verify the main file is under 120 lines. Run `bin/rubocop lib/source_monitor/configuration.rb lib/source_monitor/configuration/` to verify style compliance. Run `bin/rails test` for the full suite. Fix any RuboCop issues (likely just the frozen_string_literal pragma which should already be present).
112
- - **verify:** `wc -l lib/source_monitor/configuration.rb` shows fewer than 120 lines AND no file in `lib/source_monitor/configuration/` exceeds 300 lines AND `bin/rails test` exits 0 with 760+ runs AND `bin/rubocop lib/source_monitor/configuration.rb lib/source_monitor/configuration/` exits 0
113
- - **done:** Configuration extraction complete. All files under limits. Full suite passes. RuboCop clean.
114
-
115
- ## Verification
116
-
117
- 1. `wc -l lib/source_monitor/configuration.rb` shows fewer than 120 lines
118
- 2. `ls lib/source_monitor/configuration/*.rb | wc -l` shows 12 files
119
- 3. `bin/rails test test/lib/source_monitor/configuration_test.rb` exits 0
120
- 4. `bin/rails test` exits 0 (no regressions)
121
- 5. `bin/rubocop lib/source_monitor/configuration.rb lib/source_monitor/configuration/` exits 0
122
-
123
- ## Success Criteria
124
-
125
- - [ ] Configuration main file under 120 lines (down from 655)
126
- - [ ] 12 extracted files in lib/source_monitor/configuration/
127
- - [ ] No extracted file exceeds 300 lines
128
- - [ ] Public API unchanged -- SourceMonitor.configure { |c| c.http.timeout = 30 } works
129
- - [ ] All existing configuration tests pass without modification (860 lines)
130
- - [ ] Full test suite passes (760+ runs, 0 failures)
131
- - [ ] RuboCop passes on all modified/new files
132
- - [ ] REQ-09 satisfied
@@ -1,59 +0,0 @@
1
- # PLAN-03 Summary: extract-import-sessions-controller
2
-
3
- ## Status: COMPLETE
4
-
5
- ## Commits
6
-
7
- - **Hash:** `9dce996`
8
- - **Message:** `refactor: extract import-sessions-controller into 4 concerns [PLAN-03]`
9
- - **Files changed:** 5 files (4 new concerns + slimmed controller)
10
-
11
- ## Tasks Completed
12
-
13
- ### Task 1: Extract OpmlParser concern
14
- - Created `app/controllers/source_monitor/import_sessions/opml_parser.rb` (130 lines)
15
- - Moved OPML parsing methods: parse_opml_file, build_entry, malformed_entry, validate_upload!, etc.
16
- - Moved constants: ALLOWED_CONTENT_TYPES, GENERIC_CONTENT_TYPES, UploadError class
17
- - All 29 controller tests pass
18
-
19
- ### Task 2: Extract EntryAnnotation concern
20
- - Created `app/controllers/source_monitor/import_sessions/entry_annotation.rb` (187 lines)
21
- - Moved entry annotation methods: annotated_entries, normalize_entry, filter_entries, selectable_entries, build_selection_from_params, etc.
22
- - All 29 controller tests pass
23
-
24
- ### Task 3: Extract HealthCheckManagement concern
25
- - Created `app/controllers/source_monitor/import_sessions/health_check_management.rb` (112 lines)
26
- - Moved health check methods: start_health_checks_if_needed, reset_health_results, enqueue_health_check_jobs, health_check_progress, etc.
27
- - All 29 controller tests pass
28
-
29
- ### Task 4: Extract BulkConfiguration concern
30
- - Created `app/controllers/source_monitor/import_sessions/bulk_configuration.rb` (106 lines)
31
- - Moved bulk config methods: build_bulk_source, sample_identity_attributes, persist_bulk_settings_if_valid!, bulk_settings_payload, etc.
32
- - Controller reduced to 295 lines (target: <300)
33
- - All 29 controller tests pass
34
-
35
- ## Deviations
36
-
37
- | ID | Description | Impact |
38
- |----|-------------|--------|
39
- | None | No deviations from plan | N/A |
40
-
41
- ## Verification Results
42
-
43
- | Check | Result |
44
- |-------|--------|
45
- | `wc -l import_sessions_controller.rb` | 295 lines (target: <300) |
46
- | `ls import_sessions/*.rb \| wc -l` | 4 concern files |
47
- | `bin/rails test test/controllers/.../import_sessions_controller_test.rb` | 29 runs, 133 assertions, 0 failures, 0 errors |
48
- | `bin/rubocop import_sessions_controller.rb import_sessions/` | 5 files inspected, 0 offenses |
49
-
50
- ## Success Criteria
51
-
52
- - [x] ImportSessionsController main file under 300 lines (295, down from 792)
53
- - [x] Four concern modules created in app/controllers/source_monitor/import_sessions/
54
- - [x] No concern file exceeds 300 lines (largest: entry_annotation.rb at 187)
55
- - [x] All wizard routes and step handling preserved
56
- - [x] All existing controller tests pass without modification (29 runs, 0 failures)
57
- - [x] Full test suite passes (760 runs, 0 failures)
58
- - [x] RuboCop passes on all modified/new files
59
- - [x] REQ-10 satisfied
@@ -1,171 +0,0 @@
1
- ---
2
- phase: 3
3
- plan: 3
4
- title: extract-import-sessions-controller
5
- wave: 1
6
- depends_on: []
7
- skills_used: []
8
- must_haves:
9
- truths:
10
- - "Running `wc -l app/controllers/source_monitor/import_sessions_controller.rb` shows fewer than 300 lines"
11
- - "Running `bin/rails test test/controllers/source_monitor/import_sessions_controller_test.rb` exits 0 with zero failures"
12
- - "Running `bin/rails test` exits 0 with no regressions (760+ runs, 0 failures)"
13
- - "Running `ls app/controllers/source_monitor/import_sessions/` shows at least 4 .rb files"
14
- - "Running `ruby -c app/controllers/source_monitor/import_sessions_controller.rb` exits 0"
15
- - "Running `grep -r 'ImportSessionsController' test/ --include='*.rb' -l` shows no test files were renamed or removed"
16
- artifacts:
17
- - "app/controllers/source_monitor/import_sessions/opml_parser.rb -- OPML file parsing and validation"
18
- - "app/controllers/source_monitor/import_sessions/entry_annotation.rb -- entry annotation, filtering, selection logic"
19
- - "app/controllers/source_monitor/import_sessions/health_check_management.rb -- health check lifecycle"
20
- - "app/controllers/source_monitor/import_sessions/bulk_configuration.rb -- bulk source configuration and settings"
21
- - "app/controllers/source_monitor/import_sessions_controller.rb -- slimmed to orchestrator under 300 lines"
22
- key_links:
23
- - "REQ-10 satisfied -- ImportSessionsController broken into focused concerns"
24
- - "Public API unchanged -- all wizard routes and step handling preserved"
25
- ---
26
-
27
- # Plan 03: extract-import-sessions-controller
28
-
29
- ## Objective
30
-
31
- Extract `app/controllers/source_monitor/import_sessions_controller.rb` (792 lines) into focused concerns under `app/controllers/source_monitor/import_sessions/`. The controller has five distinct responsibility clusters beyond the core CRUD: (1) OPML file parsing and validation, (2) entry annotation/filtering/selection, (3) health check lifecycle management, (4) bulk source configuration. Each becomes a concern module included in the controller. The controller retains the action methods (new, create, show, update, destroy), step handlers, and before_action filters.
32
-
33
- ## Context
34
-
35
- <context>
36
- @app/controllers/source_monitor/import_sessions_controller.rb -- 792 lines. The OPML import wizard controller. 5-step flow: upload -> preview -> health_check -> configure -> confirm. Contains file parsing, entry annotation, health check management, bulk configuration, selection management, and user authentication fallback.
37
- @test/controllers/source_monitor/import_sessions_controller_test.rb -- 572 lines, integration tests for the wizard flow. MUST NOT be modified.
38
- @app/controllers/concerns/source_monitor/sanitizes_search_params.rb -- existing controller concern pattern in this codebase
39
- @lib/source_monitor/import_sessions/entry_normalizer.rb -- existing extracted support class for import sessions
40
- @lib/source_monitor/sources/params.rb -- source parameter handling, used by the controller
41
- @config/routes.rb -- routes for import_sessions (RESTful + step param)
42
-
43
- **Decomposition rationale:** ImportSessionsController has four clearly separable responsibility clusters:
44
- 1. **OPML parsing** (parse_opml_file, build_entry, malformed_entry, outline_attribute, valid_feed_url?, validate_upload!, content_type_allowed?, generic_content_type?, build_file_metadata, uploading_file?, UploadError) -- ~100 lines, pure data transformation
45
- 2. **Entry annotation** (annotated_entries, normalize_entry, filter_entries, selectable_entries_from, selectable_entries, build_selection_from_params, health_check_selection_from_params, advancing_from_health_check?, advancing_from_preview?, normalize_page_param) -- ~100 lines, query/presentation logic
46
- 3. **Health check management** (start_health_checks_if_needed, reset_health_results, enqueue_health_check_jobs, deactivate_health_checks!, health_check_entries, health_check_progress, health_check_complete?, health_check_targets) -- ~100 lines, async job orchestration
47
- 4. **Bulk configuration** (build_bulk_source, build_bulk_source_from_session, build_bulk_source_from_params, sample_identity_attributes, selected_entries_for_identity, fallback_identity, configure_source_params, strip_identity_attributes, persist_bulk_settings_if_valid!, bulk_settings_payload, bulk_setting_keys) -- ~90 lines, source configuration logic
48
-
49
- The remaining ~400 lines (action methods, step handlers, prepare_* context methods, user auth, step navigation) stay in the controller.
50
-
51
- **Trade-offs considered:**
52
- - Could use service objects instead of concerns. But these methods need controller context (params, render, redirect_to) or instance variables (@import_session, @current_step). Concerns are the idiomatic Rails pattern for extracting controller private methods.
53
- - Could extract each wizard step into its own concern. But that fragments the step-handling logic too much and makes the flow harder to follow. Grouping by responsibility (parsing, annotation, health, config) is more cohesive.
54
- - The user auth fallback methods (current_user_id, ensure_current_user!, fallback_user_id, create_guest_user, guest_value_for) are already inside a :nocov: block and are short (~60 lines). Leave them in the main controller -- they're security-sensitive and shouldn't be in a shared concern.
55
-
56
- **What constrains the structure:**
57
- - Concerns are placed in app/controllers/source_monitor/import_sessions/ (not in app/controllers/concerns/) because they're specific to this one controller. This follows the convention of co-locating private concerns with their controller.
58
- - Each concern is `extend ActiveSupport::Concern` and `included in` the controller
59
- - The controller must `include` the concerns and delegate appropriately
60
- - All wizard step handling (handle_upload_step, handle_preview_step, etc.) stays in the main controller because they orchestrate across concerns
61
- </context>
62
-
63
- ## Tasks
64
-
65
- ### Task 1: Extract OpmlParser concern
66
-
67
- - **name:** extract-opml-parser
68
- - **files:**
69
- - `app/controllers/source_monitor/import_sessions/opml_parser.rb` (new)
70
- - `app/controllers/source_monitor/import_sessions_controller.rb`
71
- - **action:** Create `app/controllers/source_monitor/import_sessions/` directory. Create `opml_parser.rb` containing a `SourceMonitor::ImportSessions::OpmlParser` module using `extend ActiveSupport::Concern`. Move these methods into the concern as private methods:
72
- - `parse_opml_file` (lines 305-327)
73
- - `build_entry` (lines 329-353)
74
- - `malformed_entry` (lines 355-367)
75
- - `outline_attribute` (lines 369-372)
76
- - `valid_feed_url?` (lines 374-379)
77
- - `validate_upload!` (lines 282-295)
78
- - `content_type_allowed?` (lines 297-299)
79
- - `generic_content_type?` (lines 301-303)
80
- - `build_file_metadata` (lines 255-264)
81
- - `uploading_file?` (lines 266-268)
82
-
83
- Move the constants `ALLOWED_CONTENT_TYPES`, `GENERIC_CONTENT_TYPES`, and the `UploadError` class into the concern as well. In the controller, add `include SourceMonitor::ImportSessions::OpmlParser`. Remove the moved methods and constants from the controller. Add `require "source_monitor/import_sessions/opml_parser"` or rely on Rails autoloading in the app/ directory (prefer autoloading since this is in app/).
84
- - **verify:** `ruby -c app/controllers/source_monitor/import_sessions/opml_parser.rb` exits 0 AND `bin/rails test test/controllers/source_monitor/import_sessions_controller_test.rb` exits 0
85
- - **done:** OpmlParser concern extracted. Controller includes it. Tests pass.
86
-
87
- ### Task 2: Extract EntryAnnotation concern
88
-
89
- - **name:** extract-entry-annotation
90
- - **files:**
91
- - `app/controllers/source_monitor/import_sessions/entry_annotation.rb` (new)
92
- - `app/controllers/source_monitor/import_sessions_controller.rb`
93
- - **action:** Create `entry_annotation.rb` containing a `SourceMonitor::ImportSessions::EntryAnnotation` module using `extend ActiveSupport::Concern`. Move these methods as private:
94
- - `annotated_entries` (lines 501-523)
95
- - `normalize_entry` (lines 561-564)
96
- - `filter_entries` (lines 566-575)
97
- - `selectable_entries_from` (lines 557-559)
98
- - `selectable_entries` (lines 607-609)
99
- - `build_selection_from_params` (lines 577-592)
100
- - `health_check_selection_from_params` (lines 594-605)
101
- - `advancing_from_health_check?` (lines 611-613)
102
- - `advancing_from_preview?` (lines 615-617)
103
- - `normalize_page_param` (lines 619-625)
104
- - `permitted_filter` (lines 778-783)
105
- - `preview_per_page` (lines 785-787)
106
-
107
- In the controller, add `include SourceMonitor::ImportSessions::EntryAnnotation`. Remove moved methods.
108
- - **verify:** `ruby -c app/controllers/source_monitor/import_sessions/entry_annotation.rb` exits 0 AND `bin/rails test test/controllers/source_monitor/import_sessions_controller_test.rb` exits 0
109
- - **done:** EntryAnnotation concern extracted. Tests pass.
110
-
111
- ### Task 3: Extract HealthCheckManagement concern
112
-
113
- - **name:** extract-health-check-management
114
- - **files:**
115
- - `app/controllers/source_monitor/import_sessions/health_check_management.rb` (new)
116
- - `app/controllers/source_monitor/import_sessions_controller.rb`
117
- - **action:** Create `health_check_management.rb` containing a `SourceMonitor::ImportSessions::HealthCheckManagement` module. Move these methods as private:
118
- - `start_health_checks_if_needed` (lines 627-660)
119
- - `reset_health_results` (lines 753-761)
120
- - `enqueue_health_check_jobs` (lines 763-767)
121
- - `deactivate_health_checks!` (lines 769-776)
122
- - `health_check_entries` (lines 525-532)
123
- - `health_check_progress` (lines 534-545)
124
- - `health_check_complete?` (lines 547-549)
125
- - `health_check_targets` (lines 551-555)
126
-
127
- In the controller, add `include SourceMonitor::ImportSessions::HealthCheckManagement`. Remove moved methods.
128
- - **verify:** `ruby -c app/controllers/source_monitor/import_sessions/health_check_management.rb` exits 0 AND `bin/rails test test/controllers/source_monitor/import_sessions_controller_test.rb` exits 0
129
- - **done:** HealthCheckManagement concern extracted. Tests pass.
130
-
131
- ### Task 4: Extract BulkConfiguration concern
132
-
133
- - **name:** extract-bulk-configuration
134
- - **files:**
135
- - `app/controllers/source_monitor/import_sessions/bulk_configuration.rb` (new)
136
- - `app/controllers/source_monitor/import_sessions_controller.rb`
137
- - **action:** Create `bulk_configuration.rb` containing a `SourceMonitor::ImportSessions::BulkConfiguration` module. Move these methods as private:
138
- - `build_bulk_source` (lines 675-682)
139
- - `build_bulk_source_from_session` (lines 662-665)
140
- - `build_bulk_source_from_params` (lines 667-673)
141
- - `sample_identity_attributes` (lines 684-694)
142
- - `selected_entries_for_identity` (lines 696-702)
143
- - `fallback_identity` (lines 704-709)
144
- - `configure_source_params` (lines 711-715)
145
- - `strip_identity_attributes` (lines 717-719)
146
- - `persist_bulk_settings_if_valid!` (lines 721-727)
147
- - `bulk_settings_payload` (lines 729-734)
148
- - `bulk_setting_keys` (lines 736-751)
149
-
150
- In the controller, add `include SourceMonitor::ImportSessions::BulkConfiguration`. Remove moved methods. After this task, verify the controller is under 300 lines. It should contain: before_actions, new, create, show, update, destroy, set_import_session, set_wizard_step, persist_step!, the 5 handle_*_step methods, the 4 prepare_*_context methods, state_params, permitted_step, target_step, session_attributes, auth methods, and the include statements. If the controller is between 300-320 lines, move `state_params`, `session_attributes`, `permitted_step`, and `target_step` into the EntryAnnotation concern (they're helper methods that support step navigation and parameter handling).
151
- - **verify:** `ruby -c app/controllers/source_monitor/import_sessions/bulk_configuration.rb` exits 0 AND `wc -l app/controllers/source_monitor/import_sessions_controller.rb` shows fewer than 300 lines AND `bin/rails test test/controllers/source_monitor/import_sessions_controller_test.rb` exits 0 AND `bin/rails test` exits 0 with 760+ runs AND `bin/rubocop app/controllers/source_monitor/import_sessions_controller.rb app/controllers/source_monitor/import_sessions/` exits 0
152
- - **done:** BulkConfiguration extracted. Controller under 300 lines. Full suite passes. RuboCop clean. REQ-10 satisfied.
153
-
154
- ## Verification
155
-
156
- 1. `wc -l app/controllers/source_monitor/import_sessions_controller.rb` shows fewer than 300 lines
157
- 2. `ls app/controllers/source_monitor/import_sessions/*.rb | wc -l` shows 4 files
158
- 3. `bin/rails test test/controllers/source_monitor/import_sessions_controller_test.rb` exits 0
159
- 4. `bin/rails test` exits 0 (no regressions)
160
- 5. `bin/rubocop app/controllers/source_monitor/import_sessions_controller.rb app/controllers/source_monitor/import_sessions/` exits 0
161
-
162
- ## Success Criteria
163
-
164
- - [ ] ImportSessionsController main file under 300 lines (down from 792)
165
- - [ ] Four concern modules created in app/controllers/source_monitor/import_sessions/
166
- - [ ] No concern file exceeds 300 lines
167
- - [ ] All wizard routes and step handling preserved
168
- - [ ] All existing controller tests pass without modification (572 lines)
169
- - [ ] Full test suite passes (760+ runs, 0 failures)
170
- - [ ] RuboCop passes on all modified/new files
171
- - [ ] REQ-10 satisfied