Commit graph

21 commits

Author SHA1 Message Date
Austin Schaefer
44e2fb4d4e Actually fix the Subscribe button overflow (previous fix was inert)
The prior commit's .subscribe-form .dx-input selector never matched
anything: dioxus's #[css_module] macro content-hashes Input's class at
build time (e.g. rendered as class="dx-input-83f82cbc", confirmed by
inspecting the live DOM), so a selector on the unhashed "dx-input"
name was dead CSS from the start — hence the overflow persisting
across rebuilds and hard refreshes.

Select on the raw <input> tag within .subscribe-form instead, which
doesn't depend on the module's per-build hash. Verified against the
running dev server (not just visually): the Subscribe button now
renders flush with the form's own right edge (both at x=247) instead
of spilling to x=314, well inside the sidebar's x=256 boundary.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqTJmazHBQK878vjQ4JnnF
2026-09-03 19:07:51 +02:00
Austin Schaefer
86863d809d Fix Subscribe button overflowing the sidebar's fixed width
Not an article-row or hot-reload issue after all — the Subscribe
button in the sidebar header was genuinely spilling past the sidebar's
16rem width into the main content column. The sidebar panel is
position: fixed; z-index: 10 (dx-sidebar-container in the vendored
sidebar CSS), so that overflow floated on top of whatever main content
happened to sit at the same height, which was the first article row's
button — hence it only ever looked broken there.

Root cause: .subscribe-form .dx-input had flex: 1 but no min-width: 0,
so flexbox's default min-width: auto stopped it shrinking below its
content-based minimum once the Subscribe button took its share of the
row — a classic flexbox overflow gotcha. Added min-width: 0 to both
the input and the form row.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqTJmazHBQK878vjQ4JnnF
2026-09-03 18:18:49 +02:00
Austin Schaefer
21103b1f25 Fix article row layout: button overlapping title, uneven summary gaps
article-row had no layout structure at all — title link, score badge,
and the dismiss button were plain inline siblings in a block <li>, so a
long title pushed the button flush against (or past) the row's edge.
Group title+score into a header row and make the row itself a flex
column so summary/topics/button always stack on their own line below,
regardless of title length.

Also stopped rendering the summary <p> and topics div at all when
there's no content (many RSS entries have no description), instead of
emitting empty elements — spacing was uneven because those still
reserved a line's worth of height. Left this as an explicit Rust-side
branch rather than CSS :empty: an empty <p> still contains a
zero-length text node, and whether :empty matches that is
inconsistent across engines, whereas the topics div (an empty `for`
loop) has zero children and would be a safe :empty candidate — but
kept both on the same explicit mechanism for consistency.

Swapped the dismiss button from a raw <button> to the dx Button
component, matching the rest of the row and the project's preference
for the component library over raw elements.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqTJmazHBQK878vjQ4JnnF
2026-09-03 18:08:14 +02:00
Austin Schaefer
8edffe8973 Add unit tests for list_ranked_articles' feed_id filtering
The sidebar's per-feed nav depends on this query correctly scoping to
one feed (or joining all of them when feed_id is None), and it's real
branching logic rather than a passthrough — exactly what the updated
Definition of Done's testing rule calls for.

Uses a throwaway SQLite file per test (migrated fresh, cleaned up via
Drop) rather than a shared fixture, since diesel-async's bb8 pool would
otherwise hand out per-connection ":memory:" databases that don't share
state.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqTJmazHBQK878vjQ4JnnF
2026-09-03 17:04:01 +02:00
Austin Schaefer
6a2bf8c9a8 Add feed sidebar navigation, built from Dioxus's component library
Default view stays the all-feeds joined article list, now with a sidebar
listing every subscribed feed so a reader can pin down to one feed's
articles. Filtering happens server-side (list_ranked_articles now takes
an optional feed_id).

Pulled in the sidebar/badge/scroll_area (plus their sheet/skeleton/
tooltip/separator dependencies) components via `dx components add`
instead of hand-rolling nav/tag/scroll markup, matching this project's
existing pattern of using the Dioxus component library over raw
elements (see button/input).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqTJmazHBQK878vjQ4JnnF
2026-09-03 16:28:22 +02:00
Austin Schaefer
64c46655d7 Merge remote-tracking branch 'origin/main' into worktree-feed-polling
All checks were successful
CI / check (pull_request) Successful in 1m35s
CI / test (pull_request) Successful in 3m8s
CI / audit (pull_request) Successful in 13s
# Conflicts:
#	crates/db/src/lib.rs
2026-09-03 13:45:53 +02:00
Austin Schaefer
0612d642c9 Split db crate into per-domain modules (feeds/articles/reading_events/affinities)
All checks were successful
CI / check (pull_request) Successful in 2m16s
CI / test (pull_request) Successful in 4m13s
CI / audit (pull_request) Successful in 15s
feedsignal-db's Db impl had grown into one 250-line block mixing feed
subscriptions, article storage/scoring, reading-event logging, and
topic-affinity persistence in a single file. Split each domain's
methods into its own module (mirroring the earlier web crate SRP
split), keeping lib.rs to just the Db struct and connection setup.
Pure move — no behavior or public API changes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmwX8eafbMstJvt8XPSqft
2026-09-03 13:32:19 +02:00
Austin Schaefer
6fb470084d Poll subscribed feeds for new articles on a schedule
All checks were successful
CI / check (pull_request) Successful in 13m50s
CI / test (pull_request) Successful in 4m15s
CI / audit (pull_request) Successful in 15s
Feeds were already persisted, but nothing ever re-fetched them after
the initial subscribe — scoring.rs had a TODO where feed polling was
supposed to go. Add a feed_polling job that re-fetches every
subscribed feed, relies on articles.url's unique constraint to skip
ones already seen, and stamps last_fetched_at. Runs once on startup
(so reopening the app catches up immediately) and every 15 minutes
after, ahead of the scoring pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmwX8eafbMstJvt8XPSqft
2026-09-03 13:20:30 +02:00
Austin Schaefer
44b9724645 Split web crate into layered modules (api/services/jobs/view)
All checks were successful
CI / check (pull_request) Successful in 8m13s
CI / test (pull_request) Successful in 2m36s
CI / audit (pull_request) Successful in 11s
Separates controllers (api/, the #[server] endpoints) from business
services (server/services/), background scheduling (server/jobs/,
renamed from pipeline.rs), infra bootstrap/config (server/mod.rs,
server/config.rs), and view components (app/), each one file per
responsibility instead of the previous server.rs/app.rs/pipeline.rs
grab-bags.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoXS1ERDGC1P189RqmUxAF
2026-09-03 12:03:15 +02:00
Austin Schaefer
f28ed7b758 Fix CI: cargo fmt and clippy -D warnings failures
All checks were successful
CI / check (pull_request) Successful in 6m40s
CI / test (pull_request) Successful in 3m18s
CI / audit (pull_request) Successful in 11s
- cargo fmt: reformat a match arm in app.rs and drop trailing blank
  lines the dx-components generator left in two mod.rs files
- clippy -D warnings (feedsignal-web, server feature): the generated
  ButtonVariant/ButtonSize enums have variants this app doesn't use yet
  (they're part of the component library's API surface, not dead code
  in the ordinary sense) — clippy's dead_code lint was failing the
  build under -D warnings, fixed with a scoped #[allow(dead_code)]

Verified all three CI jobs locally: cargo fmt --check, clippy for
native crates + both feedsignal-web feature sets, cargo test
--workspace --exclude feedsignal-web, and cargo audit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJJQb2DWZZwQ1yPiaAQQoY
2026-08-21 15:41:16 +02:00
Austin Schaefer
e621720e47 Split has_content test into three cases per reviewer request
Some checks failed
CI / check (pull_request) Failing after 34s
CI / test (pull_request) Has been skipped
CI / audit (pull_request) Has been skipped
Separate tests for empty/whitespace/non-blank input so a regression
points at the exact case that broke instead of a single bundled test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJJQb2DWZZwQ1yPiaAQQoY
2026-08-21 15:38:52 +02:00
Austin Schaefer
8d316408b8 Address review feedback on manual feed subscription
Some checks failed
CI / check (pull_request) Failing after 16s
CI / test (pull_request) Has been skipped
CI / audit (pull_request) Has been skipped
- Annotate the form onsubmit event type for clarity
- Disable the Subscribe button while the URL field is blank
- Rename title -> feed_title and invert the blank-title filter into a
  positively-named has_content helper, with a unit test
- Guard fetch_feed with error_for_status() so a well-formed URL that
  points at nothing (404/5xx) surfaces a clear error instead of an
  opaque feed-rs parse failure
- Collapse subscribe_feed_impl's two upsert_feed writes into one: fetch
  under a throwaway id first, then let upsert_feed be the single source
  of truth for the real feed id (existing id on re-subscribe, fresh
  otherwise), remapping fetched articles onto it before inserting. This
  also means a failed subscribe no longer leaves a placeholder row behind.
- Move the blank-URL check ahead of trimming

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJJQb2DWZZwQ1yPiaAQQoY
2026-08-21 15:32:39 +02:00
Austin Schaefer
d40c90552c Add manual feed subscription
Some checks failed
CI / check (pull_request) Failing after 32s
CI / test (pull_request) Has been skipped
CI / audit (pull_request) Has been skipped
Adds a URL input + Subscribe button (dx-components Input/Button) to the
UI, wired to a new subscribe_feed server function that upserts the feed
row and does an immediate first fetch so the reader isn't empty until
the next scheduled poll. feedsignal_feeds::fetch_feed now also returns
the feed's title, used to replace the URL placeholder once fetched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJJQb2DWZZwQ1yPiaAQQoY
2026-08-21 15:15:50 +02:00
Austin Schaefer
6445f32f8a chore: Use valid license SPDX id. Revert logic refactor.
All checks were successful
CI / check (pull_request) Successful in 28m11s
CI / test (pull_request) Successful in 5m58s
CI / audit (pull_request) Successful in 20s
2026-08-21 14:03:10 +02:00
Austin Schaefer
6fc33f4854 chore: Change license, re-position test comments, add gitignore entries.
Some checks failed
CI / check (pull_request) Failing after 32s
CI / test (pull_request) Has been skipped
CI / audit (pull_request) Has been skipped
2026-08-21 13:53:49 +02:00
Austin Schaefer
279125abb5 Merge branch 'main' of ssh://51.15.208.55:22222/schaefera/feedsignal 2026-08-21 13:53:00 +02:00
Austin Schaefer
341ff9517a Apply cargo fmt across the workspace
Some checks failed
CI / test (pull_request) Has been cancelled
CI / audit (pull_request) Has been cancelled
CI / check (pull_request) Has been cancelled
Needed for the new fmt-check CI step to pass; the repo had never had
formatting enforced before. No logic changes.
2026-08-21 13:10:08 +02:00
Austin Schaefer
2a41544546 Add affinity.rs test coverage and fix decay overshoot bug
apply_feedback, decay, get_mean_affinity, and engagement_score had no
(or broken) test coverage. Also fixes decay(): subtracting a fixed
DAILY_DECAY from a score smaller than that step flipped its sign
instead of settling at zero, causing oscillation on repeated decay
passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcD2BtNqehJxchKiuodTxi
2026-08-21 11:44:59 +02:00
Austin Schaefer
a8d4599a03 Replace sqlx with Diesel + diesel-async for compile-time-checked queries
Some checks failed
CI / check (push) Failing after 10s
sqlx's query!/query_as! macros only check raw SQL strings against the live
schema; Diesel's table!-derived DSL type-checks query structure itself at
compile time. SQLite has no native async driver, so diesel-async wraps a
blocking SqliteConnection via SyncConnectionWrapper, pooled with bb8.

Migrations move from sqlx's single-file-per-migration format to Diesel's
up.sql/down.sql pairs, run transactionally via diesel_migrations against a
throwaway sync connection at boot (MigrationHarness needs a sync
Connection), giving revertable migrations that sqlx::migrate! doesn't
support.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 11:04:38 +02:00
Austin Schaefer
2b61c80143 Default LLM scoring stage to a model already pulled locally
Some checks failed
CI / check (push) Failing after 11s
Was hardcoded to llama3.1, which isn't actually on this machine.
Switched to gemma4-e4b (5.0 GB, already pulled) as a speed/quality
balance for a call made once per shortlisted article on every
pipeline run.
2026-08-20 17:11:33 +02:00
Austin Schaefer
1a4a72fc27 Scaffold feedsignal: Rust workspace for LLM-filtered RSS reader
Some checks failed
CI / check (push) Failing after 26s
Rust workspace with core (topic-affinity learning engine + relevance
scoring), db (sqlite/sqlx schema + repo), feeds (RSS/Atom fetch), llm
(rig + local Ollama embeddings/completion), and web (axum + Dioxus
fullstack UI, no separate JS stack). Two-stage relevance filtering
(embedding shortlist -> LLM judgment) and an engagement/surprise-based
topic affinity engine with daily decay. All crates compile and core's
affinity engine has passing unit tests; server and wasm client targets
of feedsignal-web both check clean.
2026-08-20 17:04:14 +02:00