Compare commits

..

28 Commits

Author SHA1 Message Date
renovate[bot]
a819283661 Update dependency org.apache.maven.plugins:maven-failsafe-plugin to v3.6.1 2026-10-26 04:33:45 +00:00
renovate[bot]
8255c1e929 Lock file maintenance 2026-10-26 00:53:26 +00:00
renovate[bot]
eb7990f8dc Update linguijs monorepo to v6.10.1 2026-10-25 16:24:22 +00:00
Jérémie Panzer
982476b2d0 Merge pull request #2343 from Athou/renovate/biomejs-biome-2.x
Update dependency @biomejs/biome to v2.6.2
2026-10-24 19:58:21 +02:00
renovate[bot]
3ec653c3b7 Update dependency @biomejs/biome to v2.6.2 2026-10-24 13:30:33 +00:00
renovate[bot]
5dc5b3795e Update dependency vite to v8.3.5 2026-10-24 08:49:46 +00:00
Jérémie Panzer
f5f77a80a3 Merge pull request #2341 from Athou/renovate/docker-login-action-4.x
Update docker/login-action action to v4.7.0
2026-10-23 20:36:02 +02:00
renovate[bot]
1d1099492e Update docker/login-action action to v4.7.0 2026-10-23 18:01:41 +00:00
Jérémie Panzer
ccc2564929 Merge pull request #2340 from Athou/renovate/com.ibm.icu-icu4j-79.x
Update dependency com.ibm.icu:icu4j to v79
2026-10-23 08:55:25 +02:00
renovate[bot]
084574ffd4 Update dependency com.ibm.icu:icu4j to v79 2026-10-22 20:43:24 +00:00
Athou
6a6978c491 release 7.4.1 2026-10-22 17:03:04 +02:00
Athou
0399375288 don't mark as read entries that the user explicitly marked as unread (#2337) 2026-10-22 09:04:57 +02:00
renovate[bot]
394346fbc1 Update dependency npm to v12.2.1 2026-10-22 01:00:28 +00:00
renovate[bot]
0feec109bd Update Node.js to v24.22.1 2026-10-21 12:22:26 +00:00
Jérémie Panzer
fdb5c9b536 Merge pull request #2339 from Athou/renovate/org.mock-server-mockserver-junit-jupiter-no-dependencies-9.x
Update dependency org.mock-server:mockserver-junit-jupiter-no-dependencies to v9.1.0
2026-10-21 14:21:44 +02:00
renovate[bot]
f7ad8aed60 Update dependency org.mock-server:mockserver-junit-jupiter-no-dependencies to v9.1.0 2026-10-21 11:48:07 +00:00
renovate[bot]
4621d7e79f Update debian:13.7 Docker digest to 4c25275 2026-10-21 05:25:57 +00:00
Jérémie Panzer
b64a49cfb0 Merge pull request #2338 from Athou/renovate/patch-mantine-monorepo
Update mantine monorepo to v9.7.3
2026-10-21 07:25:22 +02:00
renovate[bot]
aaddfbaf40 Update mantine monorepo to v9.7.3 2026-10-20 20:33:54 +00:00
Athou
994da5e1ee don't mark as read if we are still on the same entry (#2337) 2026-10-20 16:51:42 +02:00
Athou
811e282e28 mockserver already uses jackson3 2026-10-20 13:34:06 +02:00
Jérémie Panzer
892fcf8168 Merge pull request #2336 from Athou/renovate/linguijs-monorepo
Update linguijs monorepo to v6.10.0
2026-10-20 13:15:59 +02:00
renovate[bot]
032ee9243f Update linguijs monorepo to v6.10.0 2026-10-20 10:46:22 +00:00
renovate[bot]
30cdf34d56 Update quarkus.version to v3.40.1.1 2026-10-20 10:45:55 +00:00
Athou
fd52caa3fd use responseTimeout as the global timeout for the request 2026-10-20 08:47:59 +02:00
Athou
9788712808 add a global failsafe timeout to the feed fetching client 2026-10-20 08:28:15 +02:00
Jérémie Panzer
af9d086f05 Merge pull request #2335 from agross/add-oci-source-label
Add OCI source label for Renovate release-note discovery
2026-10-19 11:31:42 +02:00
Alexander Groß
fc7190868e Add OCI source label to container images 2026-10-19 10:35:18 +02:00
16 changed files with 448 additions and 405 deletions

View File

@@ -141,7 +141,7 @@ jobs:
# Docker
- name: Login to Container Registry
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
if: ${{ env.DOCKERHUB_USERNAME != '' }}
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}

View File

@@ -1,5 +1,11 @@
# Changelog
## [7.4.1]
- Fix entries being marked as read when scrolling back to an entry that was explicitly marked as unread (#2337)
- Add a global failsafe timeout to the new HTTP/2 client to prevent it from hanging indefinitely
- Add OCI source label to container images (#2335)
## [7.4.0]
- Add support for HTTP/2 when fetching feeds (#2317)

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.5.15/schema.json",
"$schema": "https://biomejs.dev/schemas/2.6.2/schema.json",
"formatter": {
"indentStyle": "space",
"indentWidth": 4,

File diff suppressed because it is too large Load Diff

View File

@@ -17,15 +17,15 @@
"dependencies": {
"@emotion/react": "11.14.0",
"@fontsource/open-sans": "5.3.0",
"@lingui/core": "6.9.1",
"@lingui/react": "6.9.1",
"@mantine/core": "9.7.2",
"@mantine/dates": "9.7.2",
"@mantine/form": "9.7.2",
"@mantine/hooks": "9.7.2",
"@mantine/modals": "9.7.2",
"@mantine/notifications": "9.7.2",
"@mantine/spotlight": "9.7.2",
"@lingui/core": "6.10.1",
"@lingui/react": "6.10.1",
"@mantine/core": "9.7.3",
"@mantine/dates": "9.7.3",
"@mantine/form": "9.7.3",
"@mantine/hooks": "9.7.3",
"@mantine/modals": "9.7.3",
"@mantine/notifications": "9.7.3",
"@mantine/spotlight": "9.7.3",
"@monaco-editor/react": "4.7.0",
"@react-querybuilder/mantine": "8.25.0",
"@reduxjs/toolkit": "2.13.0",
@@ -55,9 +55,9 @@
},
"devDependencies": {
"@babel/types": "^8.0.6",
"@biomejs/biome": "2.5.15",
"@lingui/cli": "6.9.1",
"@lingui/vite-plugin": "6.9.1",
"@biomejs/biome": "2.6.2",
"@lingui/cli": "6.10.1",
"@lingui/vite-plugin": "6.10.1",
"@testing-library/jest-dom": "7.0.1",
"@testing-library/react": "16.3.3",
"@testing-library/user-event": "14.6.8",
@@ -71,7 +71,7 @@
"babel-plugin-react-compiler": "^1.0.0",
"jsdom": "30.1.3",
"typescript": "7.0.2",
"vite": "8.3.4",
"vite": "8.3.5",
"vite-plugin-checker": "0.14.5",
"vitest": "5.0.5",
"yaml": "2.9.1"

View File

@@ -6,16 +6,16 @@
<parent>
<groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId>
<version>7.4.0</version>
<version>7.4.1</version>
</parent>
<artifactId>commafeed-client</artifactId>
<name>CommaFeed Client</name>
<properties>
<!-- renovate: datasource=node-version depName=node -->
<node.version>v24.22.0</node.version>
<node.version>v24.22.1</node.version>
<!-- renovate: datasource=npm depName=npm -->
<npm.version>12.2.0</npm.version>
<npm.version>12.2.1</npm.version>
</properties>
<build>

View File

@@ -116,6 +116,30 @@ describe("entries", () => {
expect(client.entry.mark).toHaveBeenCalledWith({ id: "3", read: true })
})
it("flags an entry marked as unread by the user until entries are reloaded", async () => {
vi.mocked(client.feed.getEntries).mockResolvedValue({
data: {
entries: [{ id: "3", read: true, markable: true } as Entry],
hasMore: false,
name: "my-feed",
errorCount: 0,
feedLink: "https://mysite.com/feed",
timestamp: 123,
ignoredReadStatus: false,
},
} as AxiosResponse<Entries>)
const store = configureStore({ reducer: reducers })
await store.dispatch(loadEntries({ source: { type: "feed", id: "feed-id" }, clearSearch: true }))
store.dispatch(markEntry({ entry: store.getState().entries.entries[0], read: false }))
expect(store.getState().entries.entries[0].keepUnread).toBe(true)
// reloading the entry list resets the flag
await store.dispatch(loadEntries({ source: { type: "feed", id: "feed-id" }, clearSearch: false }))
expect(store.getState().entries.entries[0].keepUnread).toBeUndefined()
})
it("marks all entries as read", () => {
const store = configureStore({
reducer: reducers,

View File

@@ -10,7 +10,11 @@ export interface EntrySource {
id: string
}
export type ExpendableEntry = Entry & { expanded?: boolean }
export type ExpendableEntry = Entry & {
expanded?: boolean
/** set when the user explicitly marks the entry as unread, prevents it from being marked as read again by scrolling in expanded mode until the entry list is reloaded */
keepUnread?: boolean
}
interface EntriesState {
/** selected source */
@@ -71,11 +75,13 @@ export const entriesSlice = createSlice({
builder.addCase(markEntry.pending, (state, action) => {
for (const e of state.entries.filter(e => e.id === action.meta.arg.entry.id)) {
e.read = action.meta.arg.read
if (!action.meta.arg.read) e.keepUnread = true
}
})
builder.addCase(markMultipleEntries.pending, (state, action) => {
for (const e of state.entries.filter(e => action.meta.arg.entries.some(e2 => e2.id === e.id))) {
e.read = action.meta.arg.read
if (!action.meta.arg.read) e.keepUnread = true
}
})
builder.addCase(markAllEntries.pending, (state, action) => {

View File

@@ -111,12 +111,13 @@ export function FeedEntries() {
const el = document.getElementById(Constants.dom.entryId(e))
return el && !Constants.layout.isTopVisible(el)
})
if (currentEntry) {
if (currentEntry && currentEntry.id !== selectedEntryId) {
dispatch(
selectEntry({
entry: currentEntry,
expand: false,
markAsRead: !!scrollMarks,
// don't mark as read entries that the user explicitly marked as unread
markAsRead: !!scrollMarks && !currentEntry.keepUnread,
scrollToEntry: false,
})
)
@@ -124,7 +125,7 @@ export function FeedEntries() {
})
window.addEventListener("scroll", listener)
return () => window.removeEventListener("scroll", listener)
}, [dispatch, entries, viewMode, scrollMarks, scrollingToEntry])
}, [dispatch, entries, viewMode, scrollMarks, scrollingToEntry, selectedEntryId])
useMousetrap("r", async () => await dispatch(reloadEntries()))
useMousetrap(

View File

@@ -6,14 +6,14 @@
<parent>
<groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId>
<version>7.4.0</version>
<version>7.4.1</version>
</parent>
<artifactId>commafeed-server</artifactId>
<name>CommaFeed Server</name>
<packaging>quarkus</packaging>
<properties>
<quarkus.version>3.40.1</quarkus.version>
<quarkus.version>3.40.1.1</quarkus.version>
<querydsl.version>7.7</querydsl.version>
<rome.version>2.1.0</rome.version>
<!-- renovate: depName=com.google.googlejavaformat:google-java-format -->
@@ -176,7 +176,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.6.0</version>
<version>3.6.1</version>
<executions>
<execution>
<goals>
@@ -302,7 +302,7 @@
<dependency>
<groupId>com.commafeed</groupId>
<artifactId>commafeed-client</artifactId>
<version>7.4.0</version>
<version>7.4.1</version>
</dependency>
<!-- compile-time processors -->
@@ -439,7 +439,7 @@
<dependency>
<groupId>com.ibm.icu</groupId>
<artifactId>icu4j</artifactId>
<version>78.3</version>
<version>79.1</version>
</dependency>
<dependency>
<groupId>net.sourceforge.cssparser</groupId>
@@ -481,8 +481,8 @@
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-junit-jupiter</artifactId>
<version>7.6.0</version>
<artifactId>mockserver-junit-jupiter-no-dependencies</artifactId>
<version>9.1.0</version>
<scope>test</scope>
</dependency>
<dependency>

View File

@@ -1,4 +1,6 @@
FROM ibm-semeru-runtimes:open-jdk-25.0.4.15-jre@sha256:630c738228597b4cd31d9823e479384729b03684bac23478b33fdbda13ae31b8
LABEL org.opencontainers.image.source="https://github.com/Athou/commafeed"
EXPOSE 8082
RUN mkdir -p /commafeed/data

View File

@@ -1,4 +1,6 @@
FROM debian:13.7@sha256:913f6706df59a68922d1dd08f78c2476560a8d367897200a6005b00e5f67c2d5
FROM debian:13.7@sha256:4c25275446ca371766b8b093c696782eabce06263fccacb8d5d813b3dc44b308
LABEL org.opencontainers.image.source="https://github.com/Athou/commafeed"
ARG TARGETARCH
EXPOSE 8082

View File

@@ -41,7 +41,6 @@ import org.apache.hc.core5.http.nio.AsyncRequestProducer;
import org.apache.hc.core5.http.nio.AsyncResponseConsumer;
import org.apache.hc.core5.http.nio.entity.AbstractBinAsyncEntityConsumer;
import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
import org.apache.hc.core5.util.Timeout;
import org.jboss.resteasy.reactive.common.headers.CacheControlDelegate;
import java.io.ByteArrayOutputStream;
@@ -54,6 +53,8 @@ import java.time.InstantSource;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/** Smart HTTP getter: handles gzip, ssl, last modified, and etag headers */
@Singleton
@@ -162,7 +163,6 @@ public class HttpGetter {
HttpClientContext context = HttpClientContext.create();
context.setRequestConfig(
RequestConfig.custom()
.setResponseTimeout(Timeout.of(config.httpClient().responseTimeout()))
// causes issues with some feeds
// see https://github.com/Athou/commafeed/issues/1572
// and https://issues.apache.org/jira/browse/HTTPCLIENT-2344
@@ -191,7 +191,8 @@ public class HttpGetter {
client.execute(req, resp, context, callback);
try {
Message<org.apache.hc.core5.http.HttpResponse, byte[]> message = future.get();
Message<org.apache.hc.core5.http.HttpResponse, byte[]> message =
future.get(config.httpClient().responseTimeout().toSeconds(), TimeUnit.SECONDS);
byte[] content = message.getBody();
int code = message.getHead().getCode();
@@ -245,7 +246,11 @@ public class HttpGetter {
future.cancel(true);
Thread.currentThread().interrupt();
throw new RuntimeException("interrupted while fetching " + request.getUrl(), e);
} catch (TimeoutException e) {
future.cancel(true);
throw new IOException("timed out while fetching " + request.getUrl(), e);
} catch (ExecutionException e) {
future.cancel(true);
throw Lombok.sneakyThrow(e.getCause());
}
}

View File

@@ -19,7 +19,6 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
import org.apache.hc.client5.http.async.methods.SimpleRequestProducer;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpResponse;
@@ -29,7 +28,6 @@ import org.apache.hc.core5.http.message.BasicNameValuePair;
import org.apache.hc.core5.http.nio.entity.DiscardingEntityConsumer;
import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
import org.apache.hc.core5.net.WWWFormCodec;
import org.apache.hc.core5.util.Timeout;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -37,6 +35,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@Singleton
@Slf4j
@@ -105,10 +105,6 @@ public class PushNotificationService {
}
SimpleRequestBuilder request = SimpleRequestBuilder.post(serverUrl + "/" + topic);
request.setRequestConfig(
RequestConfig.custom()
.setResponseTimeout(Timeout.of(config.httpClient().responseTimeout()))
.build());
request.addHeader("Title", feedTitle);
request.setBody(entryTitle, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8));
@@ -152,10 +148,6 @@ public class PushNotificationService {
}
SimpleRequestBuilder request = SimpleRequestBuilder.post(serverUrl + "/message");
request.setRequestConfig(
RequestConfig.custom()
.setResponseTimeout(Timeout.of(config.httpClient().responseTimeout()))
.build());
request.addHeader("X-Gotify-Key", token);
request.setBody(json.toString(), ContentType.APPLICATION_JSON);
@@ -187,10 +179,6 @@ public class PushNotificationService {
SimpleRequestBuilder request =
SimpleRequestBuilder.post("https://api.pushover.net/1/messages.json");
request.setRequestConfig(
RequestConfig.custom()
.setResponseTimeout(Timeout.of(config.httpClient().responseTimeout()))
.build());
request.setBody(
WWWFormCodec.format(params, StandardCharsets.UTF_8),
ContentType.APPLICATION_FORM_URLENCODED.withCharset(StandardCharsets.UTF_8));
@@ -205,7 +193,9 @@ public class PushNotificationService {
new BasicResponseConsumer<>(new DiscardingEntityConsumer<>()),
null);
try {
HttpResponse response = future.get().getHead();
HttpResponse response =
future.get(config.httpClient().responseTimeout().toSeconds(), TimeUnit.SECONDS)
.getHead();
if (response.getCode() >= 400) {
throw new PushNotificationException(
name + " notification failed with status " + response.getCode());
@@ -214,7 +204,11 @@ public class PushNotificationService {
future.cancel(true);
Thread.currentThread().interrupt();
throw new IOException("interrupted while sending " + name + " notification", e);
} catch (TimeoutException e) {
future.cancel(true);
throw new IOException("timed out while sending " + name + " notification", e);
} catch (ExecutionException e) {
future.cancel(true);
throw new IOException(e.getCause());
}
}

View File

@@ -37,7 +37,6 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.NoRouteToHostException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.time.Duration;
import java.time.Instant;
@@ -241,7 +240,7 @@ class HttpGetterTest {
.when(HttpRequest.request().withMethod("GET"))
.respond(HttpResponse.response().withDelay(Delay.milliseconds(1000)));
Assertions.assertThrows(SocketTimeoutException.class, () -> getter.get(this.feedUrl));
Assertions.assertThrows(IOException.class, () -> getter.get(this.feedUrl));
}
@Test

View File

@@ -5,7 +5,7 @@
<groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId>
<version>7.4.0</version>
<version>7.4.1</version>
<name>CommaFeed</name>
<packaging>pom</packaging>