3035 Commits

Author SHA1 Message Date
dependabot[bot] c4d54f87a8 build(deps): bump github.com/prometheus/client_golang in the gomod group
Bumps the gomod group with 1 update: [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang).


Updates `github.com/prometheus/client_golang` from 1.23.2 to 1.24.0
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/v1.24.0/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.23.2...v1.24.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-version: 1.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-22 17:54:02 -07:00
Fred 1d10e7a089 fix(config): accept spaces and trailing commas in list option values
MEDIA_PROXY_RESOURCE_TYPES and TRUSTED_REVERSE_PROXY_NETWORKS were
validated by splitting the raw value without trimming, while the parser
trims items and skips empty ones. Values such as "image, video" or
"192.168.0.0/16, 10.0.0.0/8", and lists with a trailing comma, failed
startup even though the parser accepts them.

Validate the parsed list instead, and reject values that contain no
items at all so that a comma-only value cannot silently clear the
default media proxy resource types.
2026-07-21 21:22:35 -07:00
Fred 0e26f12426 fix(locale): protect translation catalog from concurrent map writes
getTranslationDict lazily populates the package-level defaultCatalog
map and runs on concurrent request goroutines via template functions,
flash messages, and error translation. Two concurrent requests for
languages not yet cached triggered Go's fatal "concurrent map writes"
and killed the daemon.

Guard the catalog with a sync.RWMutex using double-checked locking so
each language is loaded once. A failed load is no longer cached as an
empty dictionary, so unknown languages now return the error on every
call; both callers treat an error and an empty dictionary identically,
so rendered output is unchanged.

Add a regression test exercising concurrent lazy population; it fails
under the race detector on the previous implementation.
2026-07-21 20:54:36 -07:00
Fred e4e7dc55a7 fix(ui): keep proxy checkbox on subscription choose error re-render
The showChooseSubscriptionPage handler never set hasProxyConfigured on
the view, so when the add_subscription template was re-rendered after a
validation or feed creation error, the fetch_via_proxy checkbox
silently disappeared from the form. Set the flag during view setup,
matching the other subscription handlers.
2026-07-21 20:39:24 -07:00
Fred c8c414e8fb fix(worker): prevent panic when jobs are pushed during graceful shutdown
Pool.Shutdown() closed the job queue channel, but jobs can still be
pushed while workers are draining: the feed scheduler ticker goroutine
is never cancelled, and the UI and API refresh handlers push from
detached goroutines that outlive the HTTP server shutdown. Any of them
sending on the closed queue panicked the process mid-shutdown.

Keep the queue channel open and instead close a dedicated shutdown
channel, guarded by sync.Once. Push now selects between delivering a
job and the shutdown signal, discarding jobs once shutdown begins, and
workers select between the queue and the shutdown signal, so they
still finish their current job before Shutdown returns.
2026-07-21 20:33:50 -07:00
Fred 82537616f5 fix(integration): stop sending empty tags to Raindrop
strings.Split("", ",") returns [""], so an empty tag configuration
attached a single empty-string tag to every bookmark saved through the
Raindrop integration. Split the configured tags with SplitSeq, trim
whitespace, drop empty items, and omit the tags field entirely from the
payload when no tags are configured.
2026-07-21 20:30:53 -07:00
Fred 308e1f966c fix(config): reject non-positive SCHEDULER_ENTRY_FREQUENCY_FACTOR values
SCHEDULER_ENTRY_FREQUENCY_FACTOR was the only scheduler option without a
validator, so 0 was accepted at startup. With the entry_frequency polling
scheduler, the factor is used as part of a divisor in ScheduleNextCheck,
and a feed with weekly entries then triggered a division-by-zero panic
inside a background worker, crashing the daemon.

Require the factor to be >= 1, matching the validation of the other
scheduler options, so the misconfiguration fails at startup instead.
2026-07-20 20:54:54 -07:00
Fred fbbff63f2e fix(storage): clamp non-positive entry query limits to the maximum
WithLimit ignored a zero limit, so /v1/entries?limit=0 produced a query
without a SQL LIMIT and returned every matching entry, bypassing the
1000-entry cap. The official API client sends limit=0 for any filter
with an unset Limit field, making unbounded queries easy to trigger.

Clamp non-positive values to the maximum in both WithLimit and
WithLimitAndMaximum so every caller (REST API, Google Reader) stays
bounded, as intended by 0909323a.
2026-07-20 20:28:45 -07:00
Fred c342187b7e fix(handler): apply no_media_player option during feed creation
CreateFeed copied every other option from the feed creation request but
never NoMediaPlayer, so the flag was silently dropped and always stored
as false when creating a feed.
2026-07-20 20:17:34 -07:00
Fred 5c62899df9 fix(validator): allow clearing the feed proxy URL
An empty proxy_url in a feed modification request was rejected with
error.proxy_url_not_empty, so a proxy URL could never be unset once
configured. Accept the empty string to clear it, matching feed
creation, and only validate non-empty values.
2026-07-20 20:09:13 -07:00
Fred a21029dfb7 fix(validator): restrict entry_sorting_order to database enum values
ValidateUserModification reused ValidateEntryOrder, which accepts nine
sorting fields valid for the entry-listing order query parameter, while
the users.entry_order column is an entry_sorting_order enum allowing
only published_at and created_at. Requests such as
PUT /v1/users/{id} with entry_sorting_order=title passed validation and
failed in PostgreSQL, returning 500 instead of 400.

Validate the user preference against the enum values with a dedicated
validateEntrySortingOrder function and keep ValidateEntryOrder for the
entry-listing endpoint.
2026-07-20 19:57:03 -07:00
Fred 4237f8b090 fix(storage): return correct total when offset is beyond the last entry
The total returned by GetEntriesWithCount comes from count(*) OVER(),
which is carried on the returned rows. When the requested offset lands
past the last matching row, the query returns no rows and the total was
reported as 0 even though matching entries exist, breaking clients that
paginate until offset >= total.

Fall back to a separate CountEntries() query when the page is empty and
the offset is greater than zero. With offset 0 an empty result genuinely
means zero matches, so the single-query fast path is unchanged for
normal requests.

Add an integration test requesting the page at offset == total, which
must return no entries while keeping the same total.
2026-07-20 19:48:32 -07:00
Fred c119273b89 fix(storage): use <> ALL when counting remaining categories
The guard in RemoveAndReplaceCategoriesByName counted categories with
"title != ANY($2)", which is true whenever the title differs from at
least one element of the array. With two or more titles in the list,
every category matched — including the ones being deleted — so the
"at least 1 category must remain" check could pass even when the
deletion would remove all of the user's categories, leaving feeds
with a NULL category. Using "title <> ALL($2)" counts only the
categories that would actually survive the deletion.
2026-07-20 19:40:11 -07:00
Fred 92057dde56 fix(server): restrict unix socket permissions to 0660
The listening Unix socket was created world-writable (0666), allowing
any local user to connect. Restrict it to the owner and group.

Deployments where the reverse proxy runs as a different user now need
that user to share a group with the Miniflux process.
2026-07-20 18:15:02 -07:00
Fred 4d84eee221 fix(api): scope icon lookup by user in icons endpoint
The GET /v1/icons/{iconID} endpoint fetched icons by their internal
numeric identifier without any user scoping, allowing any authenticated
user to enumerate icon IDs and read favicon data associated with feeds
owned by other users on the same instance.

Rename IconByID to IconByUserAndIconID and gate the lookup on an EXISTS
check against feeds owned by the requesting user, so unauthorized icon
IDs return 404. Add integration tests covering cross-user access and
inexisting icon IDs.
2026-07-19 20:26:49 -07:00
Dave Marquard aa509b8802 feat(ui): use SVG for browser favicon 2026-07-17 21:07:08 -07:00
Ingmar Stein 56eb812b29 feat(server): reload TLS certificates on SIGHUP 2026-07-16 20:29:54 -07:00
jvoisin 69a788ab35 feat(finder): find github feeds 2026-07-16 20:21:15 -07:00
jvoisin 706a92e700 refactor(integration): factorize JSON request construction
Every integration talking to a JSON API hand-rolled the same dance:
json.Marshal the payload, build an *http.Request, set the Content-Type and
User-Agent headers, then run it through NewClientWithOptions. This is
near-identical copy-paste, so any change done to how requests are done had to
be repeated in each one.

This commit introduces a small request builder in internal/http/client and
route every integration through it:

```
client.NewRequestBuilder(endpoint).
	WithMethod(http.MethodPost).
	WithJSON(payload).
	WithHeaders(extraHeaders).
	Do()
```

Centralizing the logic also evens out disparities that had crept in between
integrations:

- private networks are now blocked everywhere, honoring
  INTEGRATION_ALLOW_PRIVATE_NETWORKS; notion previously did not block them;
- request errors are wrapped consistently with %w instead of a mix of %v/%w;
- the per-integration defaultClientTimeout constant is replaced by a single
  shared default in the client package;
- the Content-Type and Miniflux User-Agent headers are always set.

Reviewed-By: gudvinr
2026-07-16 19:53:35 -07:00
jvoisin 0939195966 perf(tests): speed up fetcher timeout test
TestRequestBuilder_TimeoutConfiguration dominated the whole test suite
at ~2s. The handler slept a fixed 2s, and httptest.Server.Close() blocks
until in-flight handlers finish, so the package always paid the full
sleep even though the client timed out at 1s.

Block the handler on <-r.Context().Done() instead: when the client times
out and drops the connection, the request context is cancelled and the
handler returns immediately, so Close() no longer waits. Shrink the
timeout to 100ms as well.
2026-07-16 19:24:13 -07:00
jvoisin ff01425686 test(api): run integration tests in parallel
Every integration tests in api_integration_test.go run sequentially against a
live server and are network-bound, taking around one minutes on my machine.

Each test is already isolated: it creates its own random-username user
and operates through that user's own client, with feeds/categories/
entries scoped per user. The shared admin client is only used read-only
or on error-path tests that create no durable state, and no test asserts
global counts, so the suite is safe to parallelize.

Add t.Parallel() to every test. No t.Setenv/Chdir or t.Run subtests are
present, so nothing conflicts with parallel execution.

It speeds things up from ~60s to ~7s on my machine. I ran the full testsuite a
handful of times just to be sure™, but didn't get any flakiness.
2026-07-16 19:21:07 -07:00
Fred 79d920bc1a fix(database): skip orphaned entries in migration v127
Databases restored from a dump without foreign key enforcement can
contain entries whose feed no longer exists. Migration v127 copied
those rows into entry_tombstones, violating its feed_id foreign key
and aborting the upgrade at schema version 126.

Only backfill tombstones for feeds that still exist: a tombstone for
a deleted feed is never consulted, and the orphaned rows are removed
by the DELETE that follows.

Fixes #4432
2026-07-13 20:23:11 -07:00
TowyTowy cf5ae57d9a fix(rewrite): keep content order when remove_tables unwraps tables
The remove_tables rule unwrapped each table element by appending its
inner HTML to the end of the parent node and then removing the element.
Any content located after the table (and the table content itself) was
therefore moved to the bottom of the entry instead of staying in place,
which reordered the article.

Replace the append-then-remove with an in-place ReplaceWithHtml so the
unwrapped content keeps its original document order. The existing
TestRewriteRemoveTables still passes because its content is fully nested
in a single root table; a new test with content surrounding the table
covers the regression.

Fixes #3110

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 20:47:37 -07:00
Saleh 070bc9ef3d fix(storage): respect category hide_globally when marking all as read
The "Mark all as read" button on the Unread page calls
MarkGloballyVisibleFeedsAsRead, which only filtered on feeds.hide_globally
and ignored the feed's category. Entries belonging to a category marked as
hidden from the global unread list were therefore marked as read even though
they are not shown on that page.

Join the categories table and exclude both feeds and categories that are
hidden globally, matching the visibility rules already used by the unread
query builder, the pagination builder and the navigation unread counter.

Fixes #4444
2026-07-09 20:21:34 -07:00
jvoisin e7888e3d43 perf(ui): defer off-screen layout/paint on entry and feed list rows
Miniflux' default user.EntriesPerPage value is 100, so people with a lot of
feeds/feed items might have 100 items displayed by default, which is a ton of
DOM elements: multiple inline SVG icons, two <button>, optional <time>,
optional reading-time chip, category tag, … the browser had to lay out and
paint all of it on initial render even though there are only a handful of rows
visible at once.

Setting content-visibility: auto on .entry-item and .feed-item lets
the browser skip layout, paint, and accessibility-tree work for rows
that fall outside the viewport, doing it lazily as the user scrolls.
contain-intrinsic-size: auto reserves a placeholder using each row's
last rendered size so the scrollbar and anchor scrolling stay
consistent after first measurement. The value 100px was picked as on my
machine/screen/…, a feed-item is a bit less than 100px, and an entry-item is
a bit more than 80px.

This doesn't introduced any regressions in keyboard shortcuts, both from
miniflux and from the browser. The two CSS properties are supported in current
modern browsers, and are simply ignored in those that don't.

Sources used:

- https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/content-visibility
- https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/contain-intrinsic-size
2026-07-09 19:51:25 -07:00
dependabot[bot] 8528e5e650 build(deps): bump the gomod group with 6 updates
Bumps the gomod group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/coreos/go-oidc/v3](https://github.com/coreos/go-oidc) | `3.19.0` | `3.20.0` |
| [golang.org/x/crypto](https://github.com/golang/crypto) | `0.53.0` | `0.54.0` |
| [golang.org/x/image](https://github.com/golang/image) | `0.43.0` | `0.44.0` |
| [golang.org/x/net](https://github.com/golang/net) | `0.56.0` | `0.57.0` |
| [golang.org/x/term](https://github.com/golang/term) | `0.44.0` | `0.45.0` |
| [golang.org/x/text](https://github.com/golang/text) | `0.38.0` | `0.40.0` |


Updates `github.com/coreos/go-oidc/v3` from 3.19.0 to 3.20.0
- [Release notes](https://github.com/coreos/go-oidc/releases)
- [Commits](https://github.com/coreos/go-oidc/compare/v3.19.0...v3.20.0)

Updates `golang.org/x/crypto` from 0.53.0 to 0.54.0
- [Commits](https://github.com/golang/crypto/compare/v0.53.0...v0.54.0)

Updates `golang.org/x/image` from 0.43.0 to 0.44.0
- [Commits](https://github.com/golang/image/compare/v0.43.0...v0.44.0)

Updates `golang.org/x/net` from 0.56.0 to 0.57.0
- [Commits](https://github.com/golang/net/compare/v0.56.0...v0.57.0)

Updates `golang.org/x/term` from 0.44.0 to 0.45.0
- [Commits](https://github.com/golang/term/compare/v0.44.0...v0.45.0)

Updates `golang.org/x/text` from 0.38.0 to 0.40.0
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.38.0...v0.40.0)

---
updated-dependencies:
- dependency-name: github.com/coreos/go-oidc/v3
  dependency-version: 3.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod
- dependency-name: golang.org/x/crypto
  dependency-version: 0.54.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod
- dependency-name: golang.org/x/image
  dependency-version: 0.44.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod
- dependency-name: golang.org/x/net
  dependency-version: 0.57.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod
- dependency-name: golang.org/x/term
  dependency-version: 0.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod
- dependency-name: golang.org/x/text
  dependency-version: 0.40.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-09 19:36:49 -07:00
jvoisin e43381d17a perf(sanitizer): avoid Token() allocations in stripIter
Tokenizer.Token() allocates a Token struct and parses the full
attribute slice for every start tag, even though stripIter only
consumes text tokens. Switch to checking the TokenType from Next()
and calling Text() only for text tokens.

Shared by StripTags and TruncateHTML, both called per entry:

  StripTags     247KB -> 71KB/op (-71%), 12623 -> 4423 allocs (-65%)
  TruncateHTML  19KB  -> 7.9KB/op (-59%),   819 ->  297 allocs (-64%)

Both roughly 2x faster on tag-heavy input.
2026-07-07 21:33:04 -07:00
Aditya Raj Singh 5ebdc176e7 fix(css): set line-height on article headings to prevent overlap when they wrap
Article headings inherit .entry-content's fixed line-height: 1.4em (computed at
the body font-size), which is smaller than the heading font-size, so headings
that wrap onto two lines overlap. Add an explicit unitless line-height: 1.2
scoped to .entry-content headings so the leading scales with each heading's own
font-size, without affecting Miniflux's own UI headings.

Fixes #4399.
2026-07-04 16:51:19 -07:00
Saleh 0abd9e7145 fix(json): escape plain-text content_text and summary
JSON Feed 1.1 defines content_html as HTML but content_text and summary
as plain text. The adapter stored whichever was present directly in
entry.Content, which is treated as HTML everywhere downstream, so any
markup-like characters in a content_text or summary value (for example
"<tag>") were dropped by the sanitizer.

Escape content_text and summary with html.EscapeString before storing
them, leaving content_html untouched. This mirrors how the Atom 0.3
reader already escapes plain-text constructs.
2026-07-03 18:57:53 -07:00
jvoisin ef24215bde perf(response): pool on-the-fly compression writers
For every dynamic response above the 1 KiB compression threshold the
builder previously did one of:
    brotli.NewWriterV2(b.w, brotli.DefaultCompression)
    gzip.NewWriter(b.w)
    flate.NewWriter(b.w, -1)

Each constructor allocates the encoder's working set from scratch:
brotli's sliding window + hash tables (~3.5 MiB at quality 6), gzip's
CRC32 + deflate state (~800 KiB), flate's hash chains (~800 KiB).
On a busy server that's per-request churn the GC then has to clean up.

All three writer types expose Reset(dst io.Writer), which rebinds the
destination without touching the internal buffers. So put each behind a
sync.Pool and Get/Reset/Put around the existing Write+Close pair. The
constructor signatures didn't change; only Pool.Get + Reset are new.

Note that brotli.NewWriterV2 (kept from before this change) returns
*matchfinder.Writer, not *brotli.Writer, as V2 is the pure-Go encoder
built on top of github.com/andybalholm/brotli/matchfinder, where the
actual Writer type lives. Hence the matchfinder import.

On a local artificial benchmarks of a 130 KiB HTML-like payload consisting of
250 entry-list items, on a single-core it improves performances by around 10%,
and for multicore under GC pressure, ns/op is reduces by ~80% and B/op by ~99%.
2026-07-03 18:51:50 -07:00
Salih Muhammed 56d0b31cb6 fix(template): avoid race when rendering concurrently
Render bound per-request, language-specific functions (t, plural, elapsed)
onto the shared template before executing it. Doing that while other
goroutines execute the same template races on its function map and can
render a response using another request's language.

Clone the template before binding those functions so each request executes
its own copy.

Fixes #4380
2026-07-02 17:20:55 -07:00
Fred 5f710f916d feat(reader): inherit feed language on RSS, RDF, and JSON Feed entries
Entries without their own language now take the feed-level value,
matching the behaviour introduced for Atom. For JSON Feed this is
spec-mandated: an item declares a language only when it differs from
the primary language of the feed. For RSS and RDF, items are part of
the channel's content, and API consumers previously saw an empty
entries.language even when the channel declared one.

The RSS channel now also reads <dc:language>: hybrid feeds commonly
declare the channel language via Dublin Core instead of <language>.
2026-07-02 16:40:52 -07:00
Fred 65cd6cd25d fix(language): reject tags outside the BCP-47 alphabet in Normalize
Feed-declared language values were persisted and rendered with no
charset or length restriction. Normalize now rejects values longer
than 50 bytes or containing characters outside [a-z0-9-], keeping
control characters, NUL bytes (which Postgres rejects, failing the
feed refresh), and oversized values out of the database and the HTML
lang attribute. Invalid values are rejected rather than stripped,
since stripping could assemble a wrong tag ("fr, en" -> "fren").

Lower-casing is ASCII-only and done in the same pass as the charset
check: Unicode case folding maps some non-ASCII characters to ASCII
(e.g. the Kelvin sign U+212A to "k"), laundering input the filter
should reject into apparently valid tags.
2026-07-02 13:17:17 -07:00
Fred 6972be2e85 refactor(reader): move language normalization out of model
NormalizeLanguage lived in the model package but is a parse-time
input-cleaning helper: all of its callers are the feed-format adapters,
nothing in model uses it, and as a free function it never enforced a
model invariant. Parse-time normalization helpers belong under
internal/reader alongside date, sanitizer, and urlcleaner.

Move it to a new internal/reader/language package and rename it to
Normalize so call sites read language.Normalize(...). No behaviour
change.
2026-07-02 11:28:41 -07:00
Fred ab8f7f9eb4 feat(atom): inherit feed-level xml:lang on entries
Per the XML specification, xml:lang applies to the whole subtree it is
declared on, so an Atom entry without its own xml:lang attribute takes
the language declared on the feed element. The parser previously left
such entries with an empty language, and only the web UI compensated
with a template-level fallback; API consumers reading entries.language
saw an empty value even when the feed declared one.

Apply the fallback in the Atom 1.0 and 0.3 adapters so the inherited
value is persisted and exposed everywhere.

Known limitation: an explicit xml:lang="" on an entry, which the spec
defines as undefining the language for that subtree, cannot be
distinguished from an absent attribute with a plain string field and
therefore inherits as well.
2026-07-02 11:15:35 -07:00
Fred 766d298095 fix(atom): restrict language parsing to namespace-qualified xml:lang
The Language fields added for Atom 1.0 and 0.3 feeds and entries used
the struct tag `xml:"lang,attr"`, which encoding/xml matches against a
lang attribute from any namespace, with the last one in document order
winning. A feed carrying e.g. foo:lang="zz" after xml:lang="fr" would
be stored with language "zz".

Qualify the tags with the XML namespace so only xml:lang matches. As a
side effect, a bare non-standard lang attribute is now ignored instead
of being treated as the feed language.

Also document each Language field and add regression tests covering
foreign-namespace and unqualified lang attributes, plus first coverage
of xml:lang parsing for Atom 0.3.
2026-07-02 11:00:51 -07:00
dependabot[bot] 68e2655b77 build(deps): bump library/alpine
Bumps the docker group with 1 update in the /packaging/docker/alpine directory: library/alpine.


Updates `library/alpine` from 3.23 to 3.24

---
updated-dependencies:
- dependency-name: library/alpine
  dependency-version: '3.24'
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docker
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-01 21:01:43 -07:00
dependabot[bot] fe37c6ba43 build(deps): bump the github-actions group with 9 updates
Bumps the github-actions group with 9 updates:

| Package | From | To |
| --- | --- | --- |
| [actions/checkout](https://github.com/actions/checkout) | `6.0.3` | `7.0.0` |
| [actions/setup-go](https://github.com/actions/setup-go) | `6.4.0` | `6.5.0` |
| [github/codeql-action/init](https://github.com/github/codeql-action) | `4.36.1` | `4.36.2` |
| [github/codeql-action/autobuild](https://github.com/github/codeql-action) | `4.36.1` | `4.36.2` |
| [github/codeql-action/analyze](https://github.com/github/codeql-action) | `4.36.1` | `4.36.2` |
| [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) | `4.1.0` | `4.2.0` |
| [docker/build-push-action](https://github.com/docker/build-push-action) | `7.2.0` | `7.3.0` |
| [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) | `9.2.1` | `9.3.0` |
| [actions/setup-python](https://github.com/actions/setup-python) | `6.2.0` | `6.3.0` |


Updates `actions/checkout` from 6.0.3 to 7.0.0
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/df4cb1c069e1874edd31b4311f1884172cec0e10...9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0)

Updates `actions/setup-go` from 6.4.0 to 6.5.0
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/4a3601121dd01d1626a1e23e37211e3254c1c06c...924ae3a1cded613372ab5595356fb5720e22ba16)

Updates `github/codeql-action/init` from 4.36.1 to 4.36.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/87557b9c84dde89fdd9b10e88954ac2f4248e463...8aad20d150bbac5944a9f9d289da16a4b0d87c1e)

Updates `github/codeql-action/autobuild` from 4.36.1 to 4.36.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/87557b9c84dde89fdd9b10e88954ac2f4248e463...8aad20d150bbac5944a9f9d289da16a4b0d87c1e)

Updates `github/codeql-action/analyze` from 4.36.1 to 4.36.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/87557b9c84dde89fdd9b10e88954ac2f4248e463...8aad20d150bbac5944a9f9d289da16a4b0d87c1e)

Updates `docker/setup-qemu-action` from 4.1.0 to 4.2.0
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/06116385d9baf250c9f4dcb4858b16962ea869c3...96fe6ef7f33517b61c61be40b68a1882f3264fb8)

Updates `docker/build-push-action` from 7.2.0 to 7.3.0
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/f9f3042f7e2789586610d6e8b85c8f03e5195baf...53b7df96c91f9c12dcc8a07bcb9ccacbed38856a)

Updates `golangci/golangci-lint-action` from 9.2.1 to 9.3.0
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/82606bf257cbaff209d206a39f5134f0cfbfd2ee...ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a)

Updates `actions/setup-python` from 6.2.0 to 6.3.0
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/a309ff8b426b58ec0e2a45f0f869d46889d02405...ece7cb06caefa5fff74198d8649806c4678c61a1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/setup-go
  dependency-version: 6.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.36.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/autobuild
  dependency-version: 4.36.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.36.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: docker/setup-qemu-action
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: docker/build-push-action
  dependency-version: 7.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: golangci/golangci-lint-action
  dependency-version: 9.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/setup-python
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-01 21:01:20 -07:00
dependabot[bot] 0dcefa7c62 build(deps): bump github.com/andybalholm/brotli in the gomod group
Bumps the gomod group with 1 update: [github.com/andybalholm/brotli](https://github.com/andybalholm/brotli).


Updates `github.com/andybalholm/brotli` from 1.2.1 to 1.2.2
- [Commits](https://github.com/andybalholm/brotli/compare/v1.2.1...v1.2.2)

---
updated-dependencies:
- dependency-name: github.com/andybalholm/brotli
  dependency-version: 1.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: gomod
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-01 21:00:57 -07:00
Bram Duvigneau 5d56902c7f feat(client): expose feed and entry language on public Go client structs
Mirrors the new `language` JSON field on the server-side Feed and Entry
models so that third-party consumers of the Go API client can read the
value declared by feeds and entries.
2026-07-01 21:00:17 -07:00
Bram Duvigneau 212c3e13d5 feat(rdf): parse feed and item language from RDF/RSS 1.0 dc:language
Extends language parsing to RDF/RSS 1.0 feeds by reading the
Dublin Core dc:language element at both the channel and item level.
Values are normalized the same way as for RSS 2.0 and Atom.
2026-07-01 21:00:17 -07:00
Bram Duvigneau 6dcb815c25 feat(ui): emit lang attribute on rendered article surfaces
Renders lang="..." on the entry title (<h1> in detail view, <h2> in every
list view) and on the entry content <article>. The attribute prefers the
entry-level language and falls back to the feed-level language; if both
are empty, no lang= attribute is emitted (rather than lang="").
2026-07-01 21:00:17 -07:00
Bram Duvigneau d456718c05 feat(reader): parse and persist feed and entry language from RSS, Atom, and JSON Feed
Reads the language declared by each feed and entry at parse time, persists
it on new `feeds.language` and `entries.language` columns, and exposes both
via the existing Feed/Entry JSON marshalling.

Sources:
- RSS feed: <language>
- RSS item: <dc:language>
- Atom 1.0 feed/entry: xml:lang
- Atom 0.3 feed/entry: xml:lang
- JSON Feed feed/item: "language"

Values are normalized at parse time (trim + lower-case + _ -> -) so they
are directly usable as an HTML lang attribute. No strict BCP-47 validation
is performed: many real feeds use loose values, and silently dropping them
yields worse downstream behaviour than passing them through.

The refresh path treats language as feed/entry-declared metadata and always
trusts the latest fetched value.
2026-07-01 21:00:17 -07:00
gudvinr 4aa60ba23d refactor(response): use strict Accept-Encoding parser 2026-06-30 20:44:16 -07:00
gudvinr 7769fa06ef feat(response): almost standard-compliant Accept-Encoding parser 2026-06-30 20:44:16 -07:00
Kelly Norton 51f2e0d819 feat(atom): use id for entry link if it is an http URL v2.3.2 2.3.2 2026-06-25 20:56:36 -07:00
Hleb Kastseika 510d225b06 fix(ui): validate per-feed entry filter rules in web forms 2026-06-24 19:45:37 -07:00
dependabot[bot] ecdc3569e3 build(deps): bump the gomod group with 2 updates
Bumps the gomod group with 2 updates: [github.com/coreos/go-oidc/v3](https://github.com/coreos/go-oidc) and [golang.org/x/image](https://github.com/golang/image).


Updates `github.com/coreos/go-oidc/v3` from 3.18.0 to 3.19.0
- [Release notes](https://github.com/coreos/go-oidc/releases)
- [Commits](https://github.com/coreos/go-oidc/compare/v3.18.0...v3.19.0)

Updates `golang.org/x/image` from 0.42.0 to 0.43.0
- [Commits](https://github.com/golang/image/compare/v0.42.0...v0.43.0)

---
updated-dependencies:
- dependency-name: github.com/coreos/go-oidc/v3
  dependency-version: 3.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod
- dependency-name: golang.org/x/image
  dependency-version: 0.43.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-24 16:16:27 -07:00
Frédéric Guillot f96bee0d61 feat(client): add missing fields to match API server
Add Feed response fields (description, next_check_at, no_media_player,
icon, and notification fields), the no_media_player and description
fields to the feed creation and modification requests, and a Tags
filter for entry queries.
2026-06-22 17:02:31 -07:00
Frédéric Guillot 15505142fd refactor(api): move entryIDsResponse struct to messages.go
Keep all API message types together in messages.go.
2026-06-22 15:58:43 -07:00