Commit graph

59 commits

Author SHA1 Message Date
Austin Schaefer
04f628b70e Give the test job its own scoped target/ cache
All checks were successful
CI / fmt (pull_request) Successful in 13s
CI / clippy-native (pull_request) Successful in 3m31s
CI / clippy-server (pull_request) Successful in 3m46s
CI / clippy-web (pull_request) Successful in 3m23s
CI / clippy-desktop (pull_request) Successful in 4m27s
CI / test (pull_request) Successful in 7m48s
CI / audit (pull_request) Successful in 25s
test's own compiled objects are worth reusing across runs — it's the
only job needing full codegen, so it can't share anything with the
clippy jobs' check-only builds regardless of key. Scoping it to a key
the clippy jobs don't touch avoids the contention/collision problem
that came from all 5 jobs previously sharing one target/ cache key,
and since it excludes feedsignal-web, its target/ stays much smaller
than the combined cache removed earlier.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 16:06:40 +02:00
Austin Schaefer
9429ca100a Merge branch 'worktree-desktop-support' of ssh://code.austinschaefer.com:22222/schaefera/feedsignal into worktree-desktop-support
All checks were successful
CI / fmt (pull_request) Successful in 11s
CI / clippy-native (pull_request) Successful in 6m1s
CI / clippy-web (pull_request) Successful in 3m50s
CI / clippy-server (pull_request) Successful in 4m59s
CI / clippy-desktop (pull_request) Successful in 4m28s
CI / test (pull_request) Successful in 6m9s
CI / audit (pull_request) Successful in 28s
2026-09-16 15:22:45 +02:00
Austin Schaefer
9a9cd9d1d3 Stop caching target/ and fix sccache's per-run cache key
target/ was the majority of the ~1.5GB cache each of the 5 CI jobs
tarred and uploaded on every run — expensive on a single runner where
those uploads contend for the same disk/network, and pointless since
these jobs build different feature-set combinations into one
identically-keyed target dir anyway. sccache already gives compiler-
level reuse without that duplication.

sccache's cache key was keyed to github.run_id, which never gets an
exact hit, so its ~377MB also re-uploaded in full every single run
with no benefit. Key it to Cargo.lock like the registry cache instead,
so it only re-saves when dependencies actually change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 15:22:30 +02:00
d002d44a2b Merge branch 'main' into worktree-desktop-support
All checks were successful
CI / fmt (pull_request) Successful in 15s
CI / clippy-native (pull_request) Successful in 11m46s
CI / clippy-server (pull_request) Successful in 11m46s
CI / clippy-web (pull_request) Successful in 3m32s
CI / clippy-desktop (pull_request) Successful in 3m37s
CI / test (pull_request) Successful in 10m14s
CI / audit (pull_request) Successful in 21s
2026-09-16 13:00:22 +00:00
Austin Schaefer
ef37690473 Hide the desktop window's title bar and default menu
Some checks failed
CI / fmt (pull_request) Successful in 15s
CI / clippy-web (pull_request) Has been cancelled
CI / clippy-desktop (pull_request) Has been cancelled
CI / test (pull_request) Has been cancelled
CI / audit (pull_request) Has been cancelled
CI / clippy-server (pull_request) Has been cancelled
CI / clippy-native (pull_request) Has been cancelled
Extract desktop window setup into its own crates/web/src/desktop.rs
module (SRP) with a single config() function, wired into main.rs via
LaunchBuilder::desktop().with_cfg(...). Disabling window decorations
also clears dioxus-desktop's default native menu bar as a side effect,
since it exists to give a decorated window something to hang off of.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 14:59:03 +02:00
baf1037306 Merge pull request 'Upgrade SQLite' (#12) from chore/update-sqlite into main
All checks were successful
CI / check (push) Successful in 7m47s
CI / test (push) Successful in 5m31s
CI / audit (push) Successful in 19s
Reviewed-on: #12
2026-09-16 09:59:02 +00:00
Austin Schaefer
803ade78eb Upgrade SQLite
All checks were successful
CI / check (pull_request) Successful in 6m41s
CI / test (pull_request) Successful in 5m19s
CI / audit (pull_request) Successful in 18s
2026-09-16 10:46:05 +02:00
Austin Schaefer
6c1020c3ef Split CI clippy checks into parallel jobs; move desktop build deps to a dedicated runner image
Some checks failed
CI / fmt (pull_request) Successful in 9s
CI / clippy-native (pull_request) Successful in 2m47s
CI / clippy-server (pull_request) Successful in 2m41s
CI / clippy-web (pull_request) Successful in 1m38s
CI / clippy-desktop (pull_request) Has been cancelled
CI / test (pull_request) Has been cancelled
CI / audit (pull_request) Has been cancelled
The single "check" job ran fmt + four clippy variants (native, server,
web, desktop) as sequential steps, so the desktop feature's clippy check
always waited on the other three even though none of them share
dependencies. Splitting each into its own job lets the runner (capacity
2) run them concurrently instead.

Also drops the per-run `apt-get install` for the GTK/WebKit headers
dioxus-desktop needs — those are now baked into a dedicated
`rust-ci-desktop` runner image (schaefera/infrastructure#TBD) so only the
desktop job's image is bigger, not every job's.

Depends on the `rust-ci-desktop` label being registered on the ci-runner
box before this workflow's clippy-desktop job can pick up work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 13:30:36 +02:00
Austin Schaefer
67d399ad05 Add desktop platform support via a new feedsignal-web desktop feature
All checks were successful
CI / check (pull_request) Successful in 7m49s
CI / test (pull_request) Successful in 3m37s
CI / audit (pull_request) Successful in 14s
Combines dioxus's desktop (webview) renderer with the existing `server`
feature so `#[server]` functions execute in-process instead of going over
HTTP — one self-contained native binary with DB, feed polling, LLM calls,
and the scheduler, no separate server to run. main.rs picks the desktop
launcher explicitly since dioxus::launch's automatic platform detection
would otherwise prefer the (also-enabled) server platform over desktop.

Verified: cargo check/clippy pass for the new feature (native crates and
existing server/web features unaffected), and running the built binary
opens a window and completes an in-process server-fn call (DB connects,
migrations run) with no HTTP round trip. CI gets a matching desktop
clippy job with the GTK/WebKit apt packages dioxus-desktop needs to
compile on Linux.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 12:36:22 +02:00
bc07a8808f Merge pull request 'Extract vendored dioxus-primitives components into their own crate' (#10) from worktree-extract-dx-components into main
All checks were successful
CI / check (push) Successful in 4m59s
CI / test (push) Successful in 3m23s
CI / audit (push) Successful in 13s
Reviewed-on: #10
Reviewed-by: Austin Schaefer <austin.schaefer@mailo.eu>
2026-09-15 10:23:28 +00:00
Austin Schaefer
3dcf404b26 Extract vendored dioxus-primitives components into their own crate
All checks were successful
CI / check (pull_request) Successful in 15m54s
CI / test (pull_request) Successful in 10m5s
CI / audit (pull_request) Successful in 27s
crates/web/src/components/ was a byte-for-byte copy of the
DioxusLabs/components "preview" registry (a shadcn-style copy-into-
your-project source, not something published as a dependency). Move it
wholesale into a new feedsignal-dioxus-components crate so it reads as
vendored code we depend on rather than application source, and future
app-specific styling should layer on via the class/attributes props
each component already accepts instead of editing the vendored files.

No component logic changed — this is a pure move plus import-path
updates in the four app files that consumed it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 11:31:27 +02:00
b50b7e61cb Merge pull request 'Extract pure logic out of RSX components into unit-testable handlers modules' (#9) from worktree-extract-rsx-logic into main
All checks were successful
CI / check (push) Successful in 12m3s
CI / test (push) Successful in 10m43s
CI / audit (push) Successful in 48s
Reviewed-on: #9
2026-09-15 09:11:50 +00:00
Austin Schaefer
49ce3eaad8 Bump rustls to 0.23.45 to clear RUSTSEC-2026-0285
All checks were successful
CI / check (pull_request) Successful in 10m18s
CI / test (pull_request) Successful in 6m44s
CI / audit (pull_request) Successful in 25s
cargo audit was failing on main independent of this branch's changes: a
new advisory (published 2026-09-14) flags rustls 0.23.43, pulled in
transitively via dioxus-server/reqwest/rig-core, for accepting TLS 1.3
handshake messages across encryption level boundaries. The fix is
upstream in 0.23.45; no code changes needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 10:11:00 +02:00
Austin Schaefer
bd086b0288 Extract subscribe_form's submit-result mapping into a testable outcome
Some checks failed
CI / check (pull_request) Successful in 1m47s
CI / test (pull_request) Successful in 3m30s
CI / audit (pull_request) Failing after 14s
The submit closure mixed pure decision logic (what should the form show
after subscribe_feed resolves) with Dioxus signal orchestration. Splits
out resolve_submit_outcome(Result<usize, String>) -> SubmitOutcome, so
the success/failure mapping is unit-tested without a Dioxus runtime; the
component now just applies the resulting outcome to its signals.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 09:50:06 +02:00
Austin Schaefer
2ae3293c9d Fix cargo fmt formatting in subscribe_form handlers tests
Some checks failed
CI / check (pull_request) Successful in 1m52s
CI / test (pull_request) Successful in 3m29s
CI / audit (pull_request) Failing after 13s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 09:42:08 +02:00
Austin Schaefer
de6256a812 Extract pure logic out of RSX components into unit-testable handlers modules
Some checks failed
CI / check (pull_request) Failing after 21s
CI / test (pull_request) Has been skipped
CI / audit (pull_request) Has been skipped
Splits article_row.rs and subscribe_form.rs into mod.rs + component.rs +
handlers.rs (mirroring the components/*/ convention), and adds a sibling
app/handlers.rs for App's heading computation. RSX now calls into plain
functions (score_percent, normalize_feed_url, subscribed_message,
feed_heading) that are covered by unit tests, instead of computing the
same logic inline where it can't be tested without a Dioxus runtime.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 09:26:00 +02:00
de6b4578d4 Merge pull request 'Add feed sidebar navigation' (#8) from worktree-feed-sidebar-nav into main
Some checks failed
CI / check (push) Successful in 17m42s
CI / test (push) Successful in 3m45s
CI / audit (push) Failing after 18s
Reviewed-on: #8
2026-09-15 07:20:40 +00:00
Austin Schaefer
03a3c1577d Let sidebar menu buttons grow with long feed titles
Some checks failed
CI / check (pull_request) Successful in 16m45s
CI / test (pull_request) Successful in 4m46s
CI / audit (pull_request) Failing after 16s
Fixed height + overflow:hidden on .dx-sidebar-menu-button caused long
feed titles (real RSS <title> text, often longer than the custom
labels shown in other readers) to clip and visually collide with the
next row. Switch to min-height per size variant and drop the
overflow/height clipping so rows grow to fit wrapped text; the
existing scrollable sidebar-content container handles the rest.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 09:16:29 +02:00
Austin Schaefer
893e0ed251 Move RelevanceInputs into core's models.rs, wire up api/models.rs rename
All checks were successful
CI / check (pull_request) Successful in 2m5s
CI / test (pull_request) Successful in 2m43s
CI / audit (pull_request) Successful in 10s
The previous commit only carried the dto.rs -> models.rs file rename;
this carries the rest: mod.rs's import/re-export update, and
RelevanceInputs moving out of scoring.rs into models.rs (built by
score_article's callers, not score_article itself), plus an unrelated
import-order fmt fix in db/src/articles.rs.
2026-09-03 23:24:48 +02:00
Austin Schaefer
7ccc36955a Sweep dto.rs and RelevanceInputs into the models.rs convention
Some checks failed
CI / check (pull_request) Failing after 1m8s
CI / test (pull_request) Has been skipped
CI / audit (pull_request) Has been skipped
- crates/web/src/api/dto.rs -> api/models.rs (ArticleView/FeedView were
  already models-shaped content, just not named models.rs)
- crates/core/src/scoring.rs's RelevanceInputs moves into
  crates/core/src/models.rs alongside the other core domain types,
  since it's built by score_article's callers, not score_article
  itself

Also picks up an unrelated fmt fix in db/src/articles.rs (import
ordering) that landed on disk from elsewhere.
2026-09-03 23:24:35 +02:00
Austin Schaefer
cc79f59a7d Decode list_ranked_articles rows via a From<Row> impl
All checks were successful
CI / check (pull_request) Successful in 1m58s
CI / test (pull_request) Successful in 2m28s
CI / audit (pull_request) Successful in 10s
Replaces the inline .map() closure with From<Row> for
RankedArticleRow, separating "what a raw SQLite row looks like" from
the query itself.
2026-09-03 21:26:56 +02:00
Austin Schaefer
e70909af47 Add and apply a models.rs convention for data-model structs
All checks were successful
CI / check (pull_request) Successful in 2m2s
CI / test (pull_request) Successful in 3m15s
CI / audit (pull_request) Successful in 10s
Document in the DoD that data-model structs (domain models, query row
types, DTOs/views) belong in a crate's models.rs rather than the file
that produces/consumes them, once used outside that function -
matching the existing crates/core/src/models.rs pattern. Component-
local structs (Props, Styles, context) are exempt.

Apply it to db: move RankedArticleRow out of articles.rs into a new
crates/db/src/models.rs.
2026-09-03 21:23:34 +02:00
Austin Schaefer
f4b7e95d15 Merge main to pick up Definition of Done 2026-09-03 21:22:29 +02:00
Austin Schaefer
73c80491be Turn RankedArticleRow into a struct instead of a 7-field tuple
All checks were successful
CI / check (pull_request) Successful in 1m34s
CI / test (pull_request) Successful in 3m19s
CI / audit (pull_request) Successful in 12s
Tuples this large stop being readable at the call site (positional
indices like rows[0].6 give no hint what they mean); a named struct
documents each field and lets rustc catch reordering mistakes.
2026-09-03 21:20:41 +02:00
Austin Schaefer
33d2dfe011 Merge list_feeds_with_titles into list_feeds
All checks were successful
CI / check (pull_request) Successful in 5m2s
CI / test (pull_request) Successful in 3m18s
CI / audit (pull_request) Successful in 15s
Both queried the same table with near-identical loads, differing only
by an extra title column and ordering. Fold title into list_feeds'
tuple (its one prior caller, the polling job, ignores it) so there's
a single feed-listing query instead of two that can drift apart.
2026-09-03 19:49:39 +02:00
a37a93150b Merge pull request 'worktree-definition-of-done' (#7) from worktree-definition-of-done into main
Some checks failed
CI / check (push) Successful in 2m42s
CI / test (push) Failing after 18s
CI / audit (push) Has been skipped
Reviewed-on: #7
2026-09-03 17:20:38 +00:00
Austin Schaefer
f0e42e2ad5 Fix article list not scrolling: duplicate <main> broke the scroll area
All checks were successful
CI / check (pull_request) Successful in 4m55s
CI / test (pull_request) Successful in 4m2s
CI / audit (pull_request) Successful in 14s
Articles past the first screenful were unreachable — not clipped by
accident, genuinely inaccessible, since the page itself couldn't
scroll either (the sidebar wrapper is overflow: hidden by design, so
the intended scroll boundary is internal to the main content pane).

Root cause: SidebarInset already renders a <main> (class
dx-sidebar-inset, a properly height-bound flex column — main{
height:900px in a 900px viewport, flex-direction:column}), but I'd
also written an explicit `main { ... }` as ITS child, producing a
`<main><main>...</main></main>` (confirmed via a headless Playwright
probe against the live dev server, not just DevTools guesswork). The
inner <main> is just a plain flex item with the default flex: 0 1 auto,
so it sized to its own content (6500+px) instead of being constrained
by the outer one's box, and the ScrollArea inside it had nothing
bounded to scroll within.

Removed the redundant inner <main> — SidebarInset's children (the
content-header div and ScrollArea) now sit directly in its own <main>,
which is the actual flex column that needs to size them. Also pinned
ScrollArea's direction to Vertical (it defaults to Both, which was
adding an unnecessary horizontal scrollbar) and moved the sizing rule
in app.css off a class ScrollArea silently drops (confirmed via the
same probe — a caller-supplied `class` never reaches ScrollArea's
rendered DOM, only its own internal one does) onto its stable
data-scroll-direction attribute instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqTJmazHBQK878vjQ4JnnF
2026-09-03 19:17:57 +02:00
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
4846ab3bd6 Broaden Definition of Done testing rule beyond core
All checks were successful
CI / check (pull_request) Successful in 2m47s
CI / test (pull_request) Successful in 3m54s
CI / audit (pull_request) Successful in 14s
Require unit tests for non-trivial logic project-wide (db query/filter
logic, non-passthrough server::services code), not just core's pure
functions, per feedback that the original scope was too narrow. Thin
passthrough wrappers stay exempt, and genuinely DB/network-bound cases
must be called out explicitly rather than silently skipped.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqTJmazHBQK878vjQ4JnnF
2026-09-03 16:55:52 +02:00
Austin Schaefer
22d0e4f537 Add Definition of Done
Codifies the quality bar already implicit in the CI gates and existing
code patterns (crate-boundary SRP, per-domain db modules, test comment
style) so it's explicit and reviewable rather than tribal knowledge.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqTJmazHBQK878vjQ4JnnF
2026-09-03 16:42:24 +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
088821f660 Merge pull request 'Poll subscribed feeds for new articles on a schedule' (#5) from worktree-feed-polling into main
All checks were successful
CI / check (push) Successful in 6m19s
CI / test (push) Successful in 3m20s
CI / audit (push) Successful in 12s
2026-09-03 14:07:26 +00: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
75148e5119 Merge pull request 'Split db crate into per-domain modules (SRP)' (#6) from worktree-srp-refactor-db into main
All checks were successful
CI / check (push) Successful in 1m49s
CI / test (push) Successful in 3m2s
CI / audit (push) Successful in 12s
Reviewed-on: #6
2026-09-03 11:40:45 +00: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
9f65ff33b2 Merge pull request 'Split web crate into layered modules (api/services/jobs/view)' (#4) from worktree-srp-refactor-web into main
All checks were successful
CI / check (push) Successful in 13m22s
CI / test (push) Successful in 3m53s
CI / audit (push) Successful in 15s
Reviewed-on: #4
2026-09-03 11:17:15 +00: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
fa9876a1b4 Merge pull request 'Add manual feed subscription' (#3) from worktree-manual-feed-subscribe into main
All checks were successful
CI / check (push) Successful in 6m7s
CI / test (push) Successful in 2m44s
CI / audit (push) Successful in 11s
2026-08-21 14:54:26 +00: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
afe3df1401 Merge pull request 'chore/fix-some-stuff' (#2) from chore/fix-some-stuff into main
All checks were successful
CI / check (push) Successful in 2m15s
CI / test (push) Successful in 5m58s
CI / audit (push) Successful in 21s
Reviewed-on: #2
2026-08-21 12:39:32 +00: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