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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b65480547bf48a4cabf2d1c98dbd6c965a6b7342c3da362b3987b1bed3e59a5d
4
- data.tar.gz: 775abb18c5c94b5cf11e78e01c296a618c7ef884cb328f4f5f886c2d144c2f75
3
+ metadata.gz: 932dca7d7b8f754262dd37aac3cf722aee017ec53e662116dd97527ec2a8a1f3
4
+ data.tar.gz: 7d6ff568a5e3eb1cf5269736771474f19da9e82b18759232b03855735f284819
5
5
  SHA512:
6
- metadata.gz: f75e313708962d167d7b362ed4f8af42be433e28d5a9e1aa59f290d82ed12103800abaf2f904098211c351f7c3b9265af05363d2364f477e22af3e7de2bc9755
7
- data.tar.gz: ab0e7911a85c744f632d2fad3dbeb671ddb55b3e1f4cc0ed3a0dbc859e2dad21ccacdec8aff7ee45445cf84f3cfb3ddaf2ba803a7e533cacdc14b8cb3ee61ab6
6
+ metadata.gz: 1318387b90d5811d52fc5d852a2be9578515a0d69f77baa90ecc1fbab930a3d2067a1e7056f8d2bd15940de2c0799e147203b31e4d91784e3078fb0fa8edc6e5
7
+ data.tar.gz: b35f81be14c230f1865642b5aa9f8fd462791c8fe3db293bf0f6326f2fc9fc1295eaef5f9899488d298e01e6eb3e6b316c00ed53595341a17973edd50cbb7b10
@@ -15,8 +15,10 @@ These are real issues encountered in previous releases. Each step below accounts
15
15
  3. **VBW volatile files**: Files in `.vbw-planning/` (`.cost-ledger.json`, `.notification-log.jsonl`, `.session-log.jsonl`, `.hook-errors.log`) are continuously modified by VBW hooks. They should be in `.gitignore`. If they aren't, add them before proceeding.
16
16
  4. **Pre-push hook**: The VBW pre-push hook at `.git/hooks/pre-push` requires the `VERSION` file to appear in the diff for any push. For new branches, it compares the commit against the working tree -- any dirty files will trigger a false positive. For force-pushes to existing branches where `VERSION` hasn't changed since the last push, use `--no-verify`.
17
17
  5. **Single squashed commit**: Always create ONE commit on the release branch with ALL changes (version bump, changelog, Gemfile.lock, any fixes). Multiple commits cause pre-push hook issues.
18
- 6. **Diff coverage CI gate**: The `test` CI job enforces diff coverage. Any changed lines in source code (not just test files) must have test coverage. If you change source code during the release (e.g., bug fixes), you must add corresponding tests.
19
- 7. **Local main divergence after merge**: After the PR merges, local main will have different commits than origin/main (pre-squash vs merged). You must `git reset --hard origin/main` to sync -- this requires user approval since the sandbox blocks it.
18
+ 6. **Diff coverage CI gate**: The `test` CI job enforces diff coverage. Any changed lines in source code (not just test files) must have test coverage. **This applies to ALL changes in the PR diff vs main, including unpushed commits made before the release started.** If the release includes source code changes (bug fixes, features), every changed source line must be covered.
19
+ 7. **Local main divergence after merge**: After the PR merges, `gh pr merge --merge --delete-branch` will attempt to fast-forward local main. This usually succeeds automatically. If it doesn't (divergent history), you must `git reset --hard origin/main` to sync -- this requires user approval since the sandbox blocks it.
20
+ 8. **Run local checks BEFORE pushing**: Always run `bin/rubocop` and `PARALLEL_WORKERS=1 bin/rails test` locally before the first push to the release branch. Each CI roundtrip (fail → fix → amend → force-push → re-run) costs ~5 minutes. In v0.7.0, skipping local checks caused two wasted CI cycles: first for uncovered diff lines, then for a RuboCop violation in the fix.
21
+ 9. **Zsh glob nomatch**: Commands like `rm -f *.gem` fail in zsh when no files match. Always use `rm -f *.gem 2>/dev/null || true` or check existence first with `ls`.
20
22
 
21
23
  ## Step 1: Git Hygiene
22
24
 
@@ -112,7 +114,25 @@ The changelog follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) f
112
114
  2. Verify the output shows the new version: `Using source_monitor X.Y.Z (was X.Y.Z-1)`.
113
115
  3. If `bundle install` fails, resolve the issue before proceeding.
114
116
 
115
- ## Step 5: Create Release Branch with Single Squashed Commit
117
+ ## Step 5: Local Pre-flight Checks
118
+
119
+ **CRITICAL**: Run these checks BEFORE creating the release branch and pushing. Each CI failure → fix → amend → force-push cycle wastes ~5 minutes. In v0.7.0, skipping this step caused two wasted CI roundtrips.
120
+
121
+ 1. **RuboCop**: Run `bin/rubocop` and fix any violations. Auto-fix with `bin/rubocop -a` if needed. This catches lint issues (like `SpaceInsideArrayLiteralBrackets`) that would fail the CI lint job.
122
+
123
+ 2. **Tests**: Run `PARALLEL_WORKERS=1 bin/rails test` and ensure all tests pass.
124
+
125
+ 3. **Diff coverage pre-check**: If the release includes source code changes beyond version/changelog/lockfile (check with `git diff --name-only origin/main`), review those files for uncovered branches. The CI diff coverage gate will reject any changed source lines without test coverage. Common blind spots:
126
+ - Fallback/else branches in new methods
127
+ - Error handling paths
128
+ - Guard clauses
129
+ If you find uncovered source lines, write tests for them NOW before creating the release commit — it's far cheaper than a CI roundtrip.
130
+
131
+ 4. **Brakeman**: Run `bin/brakeman --no-pager` and ensure zero warnings.
132
+
133
+ Only proceed to Step 6 when all local checks pass.
134
+
135
+ ## Step 6: Create Release Branch with Single Squashed Commit
116
136
 
117
137
  **IMPORTANT**: All release changes MUST be in a single commit on the release branch. This avoids pre-push hook issues where individual commits are checked for VERSION changes.
118
138
 
@@ -124,13 +144,13 @@ The changelog follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) f
124
144
  Also stage any other files that were changed (updated skills, docs, etc.).
125
145
  3. Create a single commit:
126
146
  ```
127
- chore: release vX.Y.Z
147
+ chore(release): release vX.Y.Z
128
148
  ```
129
149
  4. Push the branch: `git push -u origin release/vX.Y.Z`
130
150
  - If the pre-push hook blocks with a false positive (e.g., VBW files dirty in working tree despite being gitignored), use `git push -u --no-verify origin release/vX.Y.Z`. This is safe because we've verified VERSION is in the commit.
131
151
  5. If the push fails for other reasons, diagnose and fix before proceeding.
132
152
 
133
- ## Step 6: Create PR
153
+ ## Step 7: Create PR
134
154
 
135
155
  1. Create the PR using `gh pr create`:
136
156
  - Title: `Release vX.Y.Z`
@@ -152,7 +172,7 @@ The changelog follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) f
152
172
  - Base: `main`
153
173
  2. Report the PR URL to the user.
154
174
 
155
- ## Step 7: Monitor CI Pipeline
175
+ ## Step 8: Monitor CI Pipeline
156
176
 
157
177
  Poll the CI status using repeated `gh pr checks <PR_NUMBER>` calls. The CI has 4 required jobs: `lint`, `security`, `test`, `release_verification` (release_verification only runs after test passes).
158
178
 
@@ -163,7 +183,7 @@ Poll the CI status using repeated `gh pr checks <PR_NUMBER>` calls. The CI has 4
163
183
 
164
184
  ### If CI PASSES (all checks green):
165
185
 
166
- Continue to Step 8.
186
+ Continue to Step 9. If Step 5 (local pre-flight) was done properly, CI should pass on the first attempt.
167
187
 
168
188
  ### If CI FAILS:
169
189
 
@@ -173,32 +193,32 @@ Continue to Step 8.
173
193
  gh run view <RUN_ID> --log-failed | tail -80
174
194
  ```
175
195
  3. **Common failure: diff coverage** -- If the `test` job fails on "Enforce diff coverage", it means changed source lines lack test coverage. Read the error to identify uncovered files/lines, write tests, and add them to the release commit.
176
- 4. **Common failure: Gemfile.lock frozen** -- If `bundle install` fails in CI with "frozen mode", you forgot to run `bundle install` locally (Step 4).
177
- 5. Present failure details to the user and ask what to do:
178
- - "Fix the issues and re-push" -- Fix issues, amend the commit (`git commit --amend --no-edit`), force push (`git push --force-with-lease --no-verify origin release/vX.Y.Z`), and restart CI monitoring.
196
+ 4. **Common failure: Gemfile.lock frozen** -- If `bundle install` fails in CI with "frozen mode", you forgot to run `bundle install` locally (Step 4). Amend the commit with the updated lockfile.
197
+ 5. **Common failure: RuboCop lint** -- If the `lint` job fails, a RuboCop violation slipped through. This should have been caught in Step 5.
198
+ 6. **IMPORTANT: When fixing CI failures, run ALL local checks again before re-pushing.** Don't just fix the one failure run `bin/rubocop` AND `PARALLEL_WORKERS=1 bin/rails test` to catch cascading issues. In v0.7.0, fixing a diff coverage failure introduced a RuboCop violation, requiring a third CI cycle.
199
+ 7. Present failure details to the user and ask what to do:
200
+ - "Fix the issues and re-push" -- Fix issues, run ALL local checks (rubocop + tests), amend the commit (`git commit --amend --no-edit`), force push (`git push --force-with-lease --no-verify origin release/vX.Y.Z`), and restart CI monitoring.
179
201
  - "Close the PR and abort" -- Close the PR, delete the branch, switch back to main.
180
202
  - "Investigate manually" -- Stop and let the user handle it.
181
203
 
182
204
  **Note on force pushes**: When force-pushing the release branch after amending, always use `--no-verify` because the pre-push hook will see the diff between old and new branch tips, and `VERSION` won't appear as changed (it's the same in both). This is expected and safe.
183
205
 
184
- ## Step 8: Auto-Merge PR
206
+ ## Step 9: Auto-Merge PR
185
207
 
186
208
  Once CI is green:
187
209
 
188
210
  1. Merge the PR: `gh pr merge <PR_NUMBER> --merge --delete-branch`
211
+ - The `--delete-branch` flag also fetches and fast-forwards local main in most cases.
189
212
 
190
- 2. **Sync local main with remote** (this is the tricky part):
191
- - The merge creates a merge commit on origin/main that doesn't exist locally.
192
- - Local main may have different commits (pre-squash) than what was merged.
213
+ 2. **Sync local main with remote**:
193
214
  - Switch to main: `git checkout main`
194
- - Try `git pull origin main`. If it fails with conflicts or divergence:
195
- - Ask the user to run `git reset --hard origin/main` (the sandbox blocks this command).
196
- - Explain this is safe because the PR is merged and all changes are on origin/main.
197
- - Verify with `git log --oneline -3` that local matches remote.
215
+ - The `gh pr merge` command usually auto-syncs local main via fast-forward. Verify with `git log --oneline -3` that local matches remote (`git rev-parse HEAD` == `git rev-parse origin/main`).
216
+ - If local is behind or diverged, try `git pull origin main`.
217
+ - If pull fails with conflicts or divergence (rare): ask the user to run `git reset --hard origin/main` (the sandbox blocks this command). Explain this is safe because the PR is merged and all changes are on origin/main.
198
218
 
199
219
  3. Report: "PR #N merged successfully."
200
220
 
201
- ## Step 9: Tag the Release
221
+ ## Step 10: Tag the Release
202
222
 
203
223
  1. Verify you're on main and synced with origin.
204
224
  2. Create an annotated tag:
@@ -212,14 +232,17 @@ Once CI is green:
212
232
  ```
213
233
  5. Report the release URL.
214
234
 
215
- ## Step 10: Build the Gem
235
+ ## Step 11: Build the Gem
216
236
 
217
- 1. Clean any old gem files: `ls source_monitor-*.gem` and remove them if found (don't error if none exist).
237
+ 1. Clean any old gem files. **Note**: zsh fails on `rm -f *.gem` when no files match due to `nomatch`. Use:
238
+ ```
239
+ find . -maxdepth 1 -name 'source_monitor-*.gem' -delete
240
+ ```
218
241
  2. Build the gem: `gem build source_monitor.gemspec`
219
242
  3. Verify the gem was built: check for `source_monitor-X.Y.Z.gem` in the project root.
220
243
  4. Show the file size: `ls -la source_monitor-X.Y.Z.gem`
221
244
 
222
- ## Step 11: Gem Push Instructions
245
+ ## Step 12: Gem Push Instructions
223
246
 
224
247
  Present the final instructions to the user:
225
248
 
@@ -29,7 +29,7 @@ After the block executes, `ModelExtensions.reload!` runs automatically to apply
29
29
 
30
30
  ## Configuration Sections
31
31
 
32
- The `config` object (`SourceMonitor::Configuration`) has 11 sub-sections plus top-level queue/job settings:
32
+ The `config` object (`SourceMonitor::Configuration`) has 13 sub-sections plus top-level queue/job settings:
33
33
 
34
34
  | Section | Accessor | Class |
35
35
  |---|---|---|
@@ -45,6 +45,7 @@ The `config` object (`SourceMonitor::Configuration`) has 11 sub-sections plus to
45
45
  | Realtime | `config.realtime` | `RealtimeSettings` |
46
46
  | Authentication | `config.authentication` | `AuthenticationSettings` |
47
47
  | Images | `config.images` | `ImagesSettings` |
48
+ | Favicons | `config.favicons` | `FaviconsSettings` |
48
49
 
49
50
  See `reference/configuration-reference.md` for every setting with types, defaults, and examples.
50
51
 
@@ -91,6 +92,14 @@ config.models.source.include_concern "MyApp::SourceExtension"
91
92
  config.models.item.validate :custom_check
92
93
  ```
93
94
 
95
+ ### Favicons (Active Storage)
96
+ ```ruby
97
+ config.favicons.enabled = true
98
+ config.favicons.fetch_timeout = 10
99
+ config.favicons.max_download_size = 512 * 1024 # 512 KB
100
+ config.favicons.retry_cooldown_days = 14
101
+ ```
102
+
94
103
  ### Realtime
95
104
  ```ruby
96
105
  config.realtime.adapter = :redis
@@ -342,6 +342,50 @@ When enabled, `DownloadContentImagesJob` is automatically enqueued after new ite
342
342
 
343
343
  ---
344
344
 
345
+ ## Favicons Settings (`config.favicons`)
346
+
347
+ Class: `SourceMonitor::Configuration::FaviconsSettings`
348
+
349
+ Controls automatic favicon fetching and storage for sources via Active Storage.
350
+
351
+ **Prerequisite:** The host app must have Active Storage installed (`rails active_storage:install` + migrations). Without Active Storage, favicons are silently disabled and colored initials placeholders are shown instead.
352
+
353
+ | Setting | Type | Default | Description |
354
+ |---|---|---|---|
355
+ | `enabled` | Boolean | `true` | Enable automatic favicon fetching |
356
+ | `fetch_timeout` | Integer | `5` | HTTP timeout for favicon requests (seconds) |
357
+ | `max_download_size` | Integer | `1048576` (1 MB) | Maximum favicon file size in bytes; larger files are skipped |
358
+ | `retry_cooldown_days` | Integer | `7` | Days to wait before retrying a failed favicon fetch |
359
+ | `allowed_content_types` | Array | `["image/x-icon", "image/vnd.microsoft.icon", "image/png", "image/jpeg", "image/gif", "image/svg+xml", "image/webp"]` | Permitted MIME types for downloaded favicons |
360
+
361
+ ### Helper Method
362
+
363
+ | Method | Returns | Description |
364
+ |---|---|---|
365
+ | `enabled?` | Boolean | Returns `true` when `enabled` is truthy AND `ActiveStorage` is defined |
366
+
367
+ ```ruby
368
+ # Customize favicon settings
369
+ config.favicons.enabled = true
370
+ config.favicons.fetch_timeout = 10
371
+ config.favicons.max_download_size = 512 * 1024 # 512 KB
372
+ config.favicons.retry_cooldown_days = 14
373
+ config.favicons.allowed_content_types = %w[image/png image/x-icon image/svg+xml]
374
+ ```
375
+
376
+ When enabled, `FaviconFetchJob` is automatically enqueued:
377
+ 1. After a new source is created (via UI or OPML import) with a `website_url`
378
+ 2. After a successful feed fetch when the source has no favicon attached and is outside the retry cooldown
379
+
380
+ The job uses `Favicons::Discoverer` which tries three strategies in order:
381
+ 1. Direct `/favicon.ico` fetch from the source's domain
382
+ 2. HTML page parsing for `<link rel="icon">`, `<link rel="apple-touch-icon">`, and similar tags (prefers largest by `sizes` attribute)
383
+ 3. Google Favicon API as a last resort
384
+
385
+ Failed attempts are tracked in the source's `metadata` JSONB column (`favicon_last_attempted_at`) to respect the cooldown period.
386
+
387
+ ---
388
+
345
389
  ## Environment Variables
346
390
 
347
391
  | Variable | Purpose |
@@ -176,6 +176,23 @@ SourceMonitor.configure do |config|
176
176
  # record.errors.add(:base, "custom error") unless record.valid_for_my_app?
177
177
  # }
178
178
 
179
+ # ===========================================================================
180
+ # Favicons (Active Storage)
181
+ # ===========================================================================
182
+ # Automatically fetch and store source favicons via Active Storage.
183
+ # Requires Active Storage in the host app (rails active_storage:install).
184
+ # Without Active Storage, favicons are silently disabled -- colored
185
+ # initials placeholders are shown instead.
186
+
187
+ # config.favicons.enabled = true # default: true
188
+ # config.favicons.fetch_timeout = 5 # seconds
189
+ # config.favicons.max_download_size = 1_048_576 # 1 MB
190
+ # config.favicons.retry_cooldown_days = 7
191
+ # config.favicons.allowed_content_types = %w[
192
+ # image/x-icon image/vnd.microsoft.icon image/png
193
+ # image/jpeg image/gif image/svg+xml image/webp
194
+ # ]
195
+
179
196
  # ===========================================================================
180
197
  # Realtime (Action Cable) Adapter
181
198
  # ===========================================================================
@@ -155,6 +155,8 @@ bin/source_monitor verify
155
155
  - [ ] Event callbacks wired for host integration
156
156
  - [ ] Realtime adapter confirmed (Solid Cable or Redis)
157
157
  - [ ] Mission Control integration enabled (if desired)
158
+ - [ ] Active Storage installed (required for favicons and image downloads)
159
+ - [ ] Favicon settings configured (`config.favicons.*`) if customization needed
158
160
 
159
161
  ## Troubleshooting
160
162
 
@@ -164,6 +164,32 @@ class ScheduleFetchesJob < ApplicationJob
164
164
  end
165
165
  ```
166
166
 
167
+ ### Lightweight Fetch Job (FaviconFetchJob)
168
+
169
+ Demonstrates multi-strategy cascade with guard clauses:
170
+
171
+ ```ruby
172
+ class FaviconFetchJob < ApplicationJob
173
+ source_monitor_queue :fetch
174
+ discard_on ActiveJob::DeserializationError
175
+
176
+ def perform(source_id)
177
+ source = Source.find_by(id: source_id)
178
+ return unless source
179
+ return unless should_fetch?(source)
180
+
181
+ result = Favicons::Discoverer.new(source: source).call
182
+ attach_favicon(source, result) if result.success?
183
+ end
184
+ end
185
+ ```
186
+
187
+ Notable patterns:
188
+ - Multiple guard clauses: source exists, Active Storage defined, no existing favicon, outside cooldown period
189
+ - Uses `Favicons::Discoverer` service with 3-strategy cascade (direct `/favicon.ico`, HTML parsing, Google API)
190
+ - Failed attempts tracked in source `metadata` JSONB (`favicon_last_attempted_at`) for retry cooldown
191
+ - Graceful degradation: host apps without Active Storage never enqueue this job
192
+
167
193
  ### Broadcast Job (SourceHealthCheckJob)
168
194
 
169
195
  Demonstrates result broadcasting:
@@ -2,6 +2,28 @@
2
2
 
3
3
  Version-specific migration notes for each major/minor version transition. Agents should reference this file when guiding users through multi-version upgrades.
4
4
 
5
+ ## 0.7.x to 0.8.0
6
+
7
+ **Key changes:**
8
+ - Default HTTP User-Agent changed from `SourceMonitor/<version>` to `Mozilla/5.0 (compatible; SourceMonitor/<version>)` with browser-like headers (Accept-Language, DNT, Referer). Prevents bot-blocking by feed servers.
9
+ - Default `max_in_flight_per_source` changed from `25` to `nil` (unlimited). If you relied on the previous default for per-source rate limiting, set it explicitly.
10
+ - Successful manual health checks on degraded sources now trigger a feed fetch for faster recovery.
11
+ - Automatic source favicons via Active Storage with multi-strategy discovery (direct `/favicon.ico`, HTML `<link>` parsing, Google Favicon API fallback)
12
+ - New configuration section: `config.favicons` with `enabled`, `fetch_timeout`, `max_download_size`, `retry_cooldown_days`, and `allowed_content_types` settings
13
+ - Colored initials placeholder shown when no favicon is available or Active Storage is not installed
14
+ - OPML imports trigger favicon fetches for each imported source with a `website_url`
15
+ - Toast notifications capped at 3 visible with "+N more" badge, click-to-expand, and "Clear all" button
16
+ - Error-level toasts auto-dismiss after 10 seconds (vs 5 seconds for info/success)
17
+
18
+ **Action items:**
19
+ 1. Re-run `bin/rails source_monitor:upgrade` to get updated initializer template
20
+ 2. If you explicitly set `config.http.user_agent`, your value is preserved. Otherwise the new browser-like default applies automatically.
21
+ 3. If you need per-source scrape rate limiting, add `config.scraping.max_in_flight_per_source = 25` (or your preferred value) to your initializer
22
+ 4. If using Active Storage, favicons are enabled by default -- no action needed
23
+ 5. If NOT using Active Storage, favicons are silently disabled -- no action needed
24
+ 6. Toast stacking is automatic -- no configuration needed
25
+ 7. No breaking changes -- all existing configuration remains valid
26
+
5
27
  ## 0.3.x to 0.4.0
6
28
 
7
29
  **Released:** 2026-02-12
data/.gitignore CHANGED
@@ -27,5 +27,15 @@
27
27
  .vbw-planning/.active-agent
28
28
  .vbw-planning/.active-agent-count
29
29
  .vbw-planning/.todo-flat-migrated
30
+ .vbw-planning/.agent-worktrees/
31
+ .vbw-planning/.cache/
32
+ .vbw-planning/.context-usage
33
+ .vbw-planning/.contracts/
34
+ .vbw-planning/.events/
35
+ .vbw-planning/.execution-state.json
30
36
  /codebase_analysis.md
37
+ /VERIFICATION.md
38
+ /test/dummy/public/assets/
39
+ /test/lib/tmp/
31
40
  *.gem
41
+ .vbw-worktrees/
data/AGENTS.md CHANGED
@@ -83,7 +83,7 @@ Store secrets (API keys, webhook tokens) in `config/credentials/` and never comm
83
83
 
84
84
  ## Claude Code Skills
85
85
 
86
- SourceMonitor ships 14 engine-specific Claude Code skills (`sm-*` prefix) covering the domain model, configuration DSL, pipeline stages, testing conventions, and more. Skills are distributed with the gem and installed into `.claude/skills/` via rake tasks:
86
+ SourceMonitor ships 15 engine-specific Claude Code skills (`sm-*` prefix) covering the domain model, configuration DSL, pipeline stages, testing conventions, and more. Skills are distributed with the gem and installed into `.claude/skills/` via rake tasks:
87
87
 
88
88
  ```bash
89
89
  bin/rails source_monitor:skills:install # Consumer skills (host app integration)
data/CHANGELOG.md CHANGED
@@ -15,6 +15,62 @@ All notable changes to this project are documented below. The format follows [Ke
15
15
 
16
16
  - No unreleased changes yet.
17
17
 
18
+ ## [0.8.0] - 2026-02-21
19
+
20
+ ### Added
21
+
22
+ - **Automatic source favicons.** Sources now display favicons next to their names in list and detail views. Favicons are fetched automatically via background job on source creation and successful feed fetches using a multi-strategy cascade: `/favicon.ico` direct fetch, HTML `<link>` tag parsing (preferring largest available), and Google Favicon API fallback. Requires Active Storage in the host app.
23
+ - New configuration section: `config.favicons` with `enabled` (default: `true`), `fetch_timeout` (5s), `max_download_size` (1MB), `retry_cooldown_days` (7), and `allowed_content_types` settings.
24
+ - Colored initials placeholder shown when no favicon is available (consistent HSL color derived from source name).
25
+ - Graceful degradation: host apps without Active Storage see placeholders only, no errors.
26
+ - OPML imports also trigger favicon fetches for each imported source with a `website_url`.
27
+ - Manual "Fetch Favicon" button on source detail pages; favicon fetch also triggered on 304 Not Modified responses when missing.
28
+ - Redirect-following in favicon discoverer for domains that redirect (e.g., `reddit.com` -> `www.reddit.com`).
29
+ - **Toast notification stacking.** Bulk operations no longer flood the screen with overlapping toasts. At most 3 toasts are visible at a time; overflow is shown as a "+N more" badge that expands the full stack on click. "Clear all" button dismisses every toast at once.
30
+ - Error-level toasts persist for 10 seconds (vs 5 seconds for info/success).
31
+ - Hidden toasts promote into visible slots as earlier toasts auto-dismiss.
32
+ - Container controller tracks DOM changes via MutationObserver and properly cleans up event listeners on disconnect.
33
+
34
+ ### Changed
35
+
36
+ - **Browser-like default User-Agent.** Default HTTP User-Agent changed from `SourceMonitor/<version>` to `Mozilla/5.0 (compatible; SourceMonitor/<version>)` with full browser-like headers (Accept, Accept-Language, DNT, Referer from source `website_url`). This prevents bot-blocking by feed servers.
37
+ - **Smarter scrape rate limiting.** Default `max_in_flight_per_source` changed from `25` to `nil` (unlimited). The previous default unnecessarily throttled scraping for sources with many items. Set an explicit value in your initializer if you need per-source caps.
38
+ - **Health check triggers status re-evaluation.** A successful manual health check on a degraded (declining/critical/warning) source now triggers a feed fetch, allowing the health monitor to transition the source back to "improving" status instead of requiring the source to recover on its own schedule.
39
+
40
+ ### Fixed
41
+
42
+ - Favicon discoverer properly follows HTTP redirects (e.g., `reddit.com` -> `www.reddit.com`).
43
+ - Favicon fetch uses `rails_blob_path` for correct routing within the engine context.
44
+ - Favicon display prefers PNG format (via Google Favicon API) over raw ICO for better browser compatibility.
45
+ - Gemspec excludes `.vbw-planning/` from gem package to reduce gem size.
46
+
47
+ ### Testing
48
+
49
+ - 1,125 tests, 0 failures.
50
+ - RuboCop: 0 offenses.
51
+ - Brakeman: 0 warnings.
52
+
53
+ ## [0.7.1] - 2026-02-18
54
+
55
+ ### Changed
56
+
57
+ - **Test suite 60% faster (118s → 46s).** Disabled Faraday retry middleware in tests — WebMock-stubbed timeout errors triggered 4 retries with exponential backoff (7.5s of real sleep per test), consuming 73% of total runtime across 11 FeedFetcher tests.
58
+ - Split monolithic FeedFetcherTest (71 tests, 84.8s) into 6 concern-based test classes for better parallelization and maintainability.
59
+ - Switched default test parallelism from fork-based to thread-based, eliminating PG segfault on single-file runs.
60
+ - Reduced test log IO by setting test log level to `:warn` (was `:debug`, generating 95MB of output).
61
+ - Adopted `setup_once`/`before_all` in 5 DB-heavy analytics/dashboard test files.
62
+ - Added `test:fast` rake task to exclude integration and system tests during development.
63
+
64
+ ### Fixed
65
+
66
+ - Suppressed spurious TestProf "before_all is not implemented for threads" warning by loading TestProf after `parallelize` call.
67
+
68
+ ### Testing
69
+
70
+ - 1,033 tests, 3,302 assertions, 0 failures.
71
+ - RuboCop: 0 offenses.
72
+ - Brakeman: 0 warnings.
73
+
18
74
  ## [0.7.0] - 2026-02-18
19
75
 
20
76
  ### Fixed
data/CLAUDE.md CHANGED
@@ -4,17 +4,17 @@
4
4
 
5
5
  ## Active Context
6
6
 
7
- **Milestone:** (none active)
8
- **Last shipped:** upgrade-assurance (2026-02-13) -- 3 phases, 14 tasks, 12 commits
9
- **Previous:** generator-enhancements (2026-02-12) -- v0.4.0
10
- **Next action:** /vbw:vibe to start a new milestone
7
+ **Milestone:** polish-and-reliability
8
+ **Phase:** 1 -- Backend Fixes (pending planning)
9
+ **Last shipped:** aia-ssl-fix (2026-02-20) -- 2 phases, 7 plans, 8 commits
10
+ **Previous:** upgrade-assurance (2026-02-13), generator-enhancements (2026-02-12)
11
11
 
12
12
  ## Key Decisions
13
13
 
14
14
  - Keep PostgreSQL-only for now
15
15
  - Keep host-app auth model
16
16
  - Ruby autoload for lib/ modules (not Zeitwerk)
17
- - PG parallel fork segfault when running single test files; use PARALLEL_WORKERS=1 or full suite
17
+ - PG parallel fork segfault resolved: switched to thread-based parallelism in aia-ssl-fix milestone
18
18
 
19
19
  ## Installed Skills
20
20
 
@@ -100,6 +100,12 @@ Run /vbw:help for all commands.
100
100
  - No N+1 queries (use `includes`/`preload`).
101
101
  - No hardcoded credentials (use Rails credentials or ENV).
102
102
 
103
+ ## QA and UAT Rules
104
+
105
+ - **Browser-first verification:** During VBW QA (`/vbw:qa`) and UAT (`/vbw:verify`), ALWAYS start by using `agent-browser` to test UI scenarios yourself before presenting checkpoints to the user. Navigate to the dummy app (port 3002), take snapshots/screenshots, and verify visual and functional behavior with agents first.
106
+ - **Automate what you can:** Any test that can be verified programmatically (config defaults, job enqueue behavior, controller responses) should be automated -- only present truly visual/interactive tests to the user.
107
+ - **Dummy app port:** The SourceMonitor dummy app runs on port 3002 (`cd test/dummy && bin/rails server -p 3002`).
108
+
103
109
  ## Security Rules
104
110
 
105
111
  ### Protected Files (NEVER read or output)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- source_monitor (0.7.0)
4
+ source_monitor (0.8.0)
5
5
  cssbundling-rails (~> 1.4)
6
6
  faraday (~> 2.9)
7
7
  faraday-follow_redirects (~> 0.4)
data/README.md CHANGED
@@ -9,8 +9,8 @@ SourceMonitor is a production-ready Rails 8 mountable engine for ingesting, norm
9
9
  In your host Rails app:
10
10
 
11
11
  ```bash
12
- bundle add source_monitor --version "~> 0.3.1"
13
- # or add `gem "source_monitor", "~> 0.3.1"` manually, then run:
12
+ bundle add source_monitor --version "~> 0.7.1"
13
+ # or add `gem "source_monitor", "~> 0.7.1"` manually, then run:
14
14
  bundle install
15
15
  ```
16
16
 
@@ -19,7 +19,9 @@ This exposes `bin/source_monitor` (via Bundler binstubs) so you can run the guid
19
19
  ## Highlights
20
20
  - Full-featured source and item administration backed by Turbo Streams and Tailwind UI components
21
21
  - Adaptive fetch pipeline (Feedjira + Faraday) with conditional GETs, retention pruning, and scrape orchestration
22
+ - Automatic source favicons via Active Storage with multi-strategy discovery and graceful fallback
22
23
  - Realtime dashboard metrics, batching/caching query layer, and Mission Control integration hooks
24
+ - Smart toast notification stacking (max 3 visible, "+N more" overflow badge, click-to-expand)
23
25
  - Extensible scraper adapters (Readability included) with per-source settings and structured result metadata
24
26
  - Declarative configuration DSL covering queues, HTTP, retention, events, model extensions, authentication, and realtime transports
25
27
  - First-class observability through ActiveSupport notifications and `SourceMonitor::Metrics` counters/gauges
@@ -41,7 +43,7 @@ This exposes `bin/source_monitor` (via Bundler binstubs) so you can run the guid
41
43
  Before running any SourceMonitor commands inside your host app, add the gem and install dependencies:
42
44
 
43
45
  ```bash
44
- bundle add source_monitor --version "~> 0.3.1"
46
+ bundle add source_monitor --version "~> 0.7.1"
45
47
  # or edit your Gemfile, then run
46
48
  bundle install
47
49
  ```
@@ -113,7 +115,7 @@ See [docs/configuration.md](docs/configuration.md) for exhaustive coverage and e
113
115
 
114
116
  ## Claude Code Skills
115
117
 
116
- SourceMonitor ships 14 engine-specific Claude Code skills (`sm-*` prefix) that give AI agents deep context about the engine's domain model, configuration DSL, pipeline stages, and testing conventions. Skills are bundled with the gem and installed into your host app's `.claude/skills/` directory.
118
+ SourceMonitor ships 15 engine-specific Claude Code skills (`sm-*` prefix) that give AI agents deep context about the engine's domain model, configuration DSL, pipeline stages, and testing conventions. Skills are bundled with the gem and installed into your host app's `.claude/skills/` directory.
117
119
 
118
120
  ```bash
119
121
  bin/rails source_monitor:skills:install # Consumer skills (host app integration)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.0
1
+ 0.8.0
@@ -651,6 +651,14 @@ video {
651
651
  right: 0px;
652
652
  }
653
653
 
654
+ .fm-admin .-bottom-1 {
655
+ bottom: -0.25rem;
656
+ }
657
+
658
+ .fm-admin .-right-1 {
659
+ right: -0.25rem;
660
+ }
661
+
654
662
  .fm-admin .left-4 {
655
663
  left: 1rem;
656
664
  }
@@ -957,6 +965,10 @@ video {
957
965
  align-items: flex-start;
958
966
  }
959
967
 
968
+ .fm-admin .items-end {
969
+ align-items: flex-end;
970
+ }
971
+
960
972
  .fm-admin .items-center {
961
973
  align-items: center;
962
974
  }
@@ -977,6 +989,10 @@ video {
977
989
  gap: 0.25rem;
978
990
  }
979
991
 
992
+ .fm-admin .gap-1\.5 {
993
+ gap: 0.375rem;
994
+ }
995
+
980
996
  .fm-admin .gap-2 {
981
997
  gap: 0.5rem;
982
998
  }
@@ -1348,6 +1364,11 @@ video {
1348
1364
  background-color: rgb(248 250 252 / var(--tw-bg-opacity, 1));
1349
1365
  }
1350
1366
 
1367
+ .fm-admin .bg-slate-700 {
1368
+ --tw-bg-opacity: 1;
1369
+ background-color: rgb(51 65 85 / var(--tw-bg-opacity, 1));
1370
+ }
1371
+
1351
1372
  .fm-admin .bg-slate-800 {
1352
1373
  --tw-bg-opacity: 1;
1353
1374
  background-color: rgb(30 41 59 / var(--tw-bg-opacity, 1));
@@ -1367,6 +1388,15 @@ video {
1367
1388
  background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
1368
1389
  }
1369
1390
 
1391
+ .fm-admin .object-contain {
1392
+ -o-object-fit: contain;
1393
+ object-fit: contain;
1394
+ }
1395
+
1396
+ .fm-admin .p-0\.5 {
1397
+ padding: 0.125rem;
1398
+ }
1399
+
1370
1400
  .fm-admin .p-3 {
1371
1401
  padding: 0.75rem;
1372
1402
  }
@@ -1726,6 +1756,10 @@ video {
1726
1756
  color: rgb(255 255 255 / var(--tw-text-opacity, 1));
1727
1757
  }
1728
1758
 
1759
+ .fm-admin .underline {
1760
+ text-decoration-line: underline;
1761
+ }
1762
+
1729
1763
  .fm-admin .opacity-0 {
1730
1764
  opacity: 0;
1731
1765
  }
@@ -1848,6 +1882,11 @@ video {
1848
1882
  background-color: rgb(248 250 252 / var(--tw-bg-opacity, 1));
1849
1883
  }
1850
1884
 
1885
+ .fm-admin .hover\:bg-slate-600:hover {
1886
+ --tw-bg-opacity: 1;
1887
+ background-color: rgb(71 85 105 / var(--tw-bg-opacity, 1));
1888
+ }
1889
+
1851
1890
  .fm-admin .hover\:bg-slate-700:hover {
1852
1891
  --tw-bg-opacity: 1;
1853
1892
  background-color: rgb(51 65 85 / var(--tw-bg-opacity, 1));
@@ -1962,6 +2001,10 @@ video {
1962
2001
  background-color: rgb(241 245 249 / var(--tw-bg-opacity, 1));
1963
2002
  }
1964
2003
 
2004
+ .fm-admin :is(.group:hover .group-hover\:inline-flex) {
2005
+ display: inline-flex;
2006
+ }
2007
+
1965
2008
  .fm-admin :is(.peer:checked ~ .peer-checked\:border-blue-500) {
1966
2009
  --tw-border-opacity: 1;
1967
2010
  border-color: rgb(59 130 246 / var(--tw-border-opacity, 1));