Skip to content

chore: merge upstream jocmp/capyreader main#2117

Closed
Shengqiang-Zhang wants to merge 118 commits into
jocmp:mainfrom
Shengqiang-Zhang:chore/merge-upstream-main
Closed

chore: merge upstream jocmp/capyreader main#2117
Shengqiang-Zhang wants to merge 118 commits into
jocmp:mainfrom
Shengqiang-Zhang:chore/merge-upstream-main

Conversation

@Shengqiang-Zhang
Copy link
Copy Markdown

Summary

Merge 94 upstream commits from jocmp/capyreader:main (through 0e42752f) into the fork. Resolves divergence accumulated since last upstream sync.

Upstream highlights

Conflict resolution

Six files conflicted; resolved as follows:

File Resolution
.gitignore, app/build.gradle.kts, app/.../ArticleScreen.kt, app/.../ArticleScreenViewModelTest.kt, tsconfig.json Kept fork version (--ours) — has fork-specific changes
capy/.../LocalAccountDelegate.kt Took upstream version — fork's only edit here (0e17e7db) targeted a function upstream had since removed (#2035)

Fixups applied to keep the build green

  • ArticleScreen.kt: added onMarkAllRead to FeedList, onRemoveFolder/currentFeed/source to ArticleListTopBar, removed refreshingAll from ArticleList, collected currentFeed from the view model
  • app/build.gradle.kts: added testImplementation(libs.tests.work.testing) for the new RefreshSchedulerTest

Test plan

  • ./gradlew assembleFreeDebug passes
  • ./gradlew :capy:test :rssparser:test :feedfinder:test :readerclient:test :feedbinclient:test all pass
  • ./gradlew :app:testFreeDebugUnitTest — could not run in sandbox (new upstream dep androidx.work:work-testing:2.11.2 not cached and dl.google.com blocked); run locally before merging
  • Smoke-test on a device: local feed refresh, Miniflux refresh, mark-read-on-scroll, drawer swipe-to-close, mark-all-read from sidebar
  • Confirm version bump is still desired — fork kept its own version line; upstream is now on 2026.05.1209

🤖 Generated with Claude Code

Shengqiang-Zhang and others added 30 commits April 16, 2026 00:00
Bundled article fonts (Inter, Jost, Literata, etc.) are Latin-only,
so CJK glyphs fall back to whatever sans-serif font WebView ships with
— which ignores OEM system font customizations (e.g. Mi Sans on MIUI,
HarmonyOS Sans). Add a new FontOption.SYSTEM_UI that maps to the CSS
system-ui stack so articles can render in the actual device system
font, including its CJK coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Triggered on v* tag push or manual dispatch. Uses repository signing
secrets when available and falls back to the committed debug keystore
so forks can publish releases without additional configuration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous SYSTEM_UI option mapped to CSS system-ui, but Chromium's
system-ui on Android resolves to a fixed Roboto/Noto stack and never
reaches OEM-registered families like Mi Sans (MIUI), HarmonyOS Sans,
OPPO Sans, etc. Native Compose UI honors Typeface.DEFAULT and picks
them up correctly; only WebView was stuck on Roboto for Latin and
Noto for CJK.

Resolve an OEM-branded or CJK-capable font via SystemFonts.getAvailableFonts(),
serve the file through a new /system-font/ WebViewAssetLoader handler, and
point an @font-face ("AndroidSystem") at it. The SYSTEM_UI family stack
now leads with AndroidSystem and also enumerates the common OEM family
names as backups before falling through to system-ui.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On Samsung S24+, the previous resolver returned null for devices whose
default font filename lacks the "samsung" substring (e.g. SECSans*,
SamsungOneUI-Regular.otf), and the path handler always served "font/ttf"
— so an .otf or .ttc face came back with the wrong MIME and Chromium
rejected it, falling through @font-face to the Roboto/Noto stack. The CSS
`format("truetype")` hint compounded this by instructing browsers to skip
non-truetype sources outright.

Broaden OEM_FILE_HINTS (oneui, secsans, seccjk, sansation, honor, magicui,
nubia), prefer non-script-specific OEM faces, and add a Latin fallback
that picks any sans-serif whose filename isn't a stock Roboto/Noto/Droid
or a script/emoji/symbol/serif/mono variant. Drop the CSS format hint so
browsers auto-detect, and select the response MIME from the file extension
(otf → font/otf, ttc → font/collection, else font/ttf). Log the resolved
file and unresolved candidates via CapyLog so future devices can be
diagnosed from the event stream.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Samsung's Galaxy Themes installs user-selected fonts (e.g. Palatino) in a
private directory that app processes can't read, so the `Real system default`
option can only reach the built-in OEM family. Add a CUSTOM FontOption that
lets the user point the article WebView at any font file on the device.

The user taps the new Custom font… item in the font dropdown, which opens a
SAF picker. CustomFontManager copies the chosen file into
`filesDir/article-custom-font` (avoiding brittle content-URI permissions) and
records the display name so the menu can echo it back. CustomFontPathHandler
serves the saved file to WebView under /custom-font/, detecting OTF/TTC from
magic bytes and falling back to TTF. A new @font-face ("CustomFont") is
wired into the SCSS alongside a `custom` slug, so selecting CUSTOM swaps the
body class to `article__body--font-custom` and the title class accordingly
when titleFollowsBodyFont is on.

Also revert the short-lived hidden-API reflection in SystemFontResolver — it
didn't help the custom-font case and added a sketchy VMRuntime bypass. The
expanded OEM hints, Latin fallback, mime-type-by-extension, and dropped
format() hint from the prior commit stay; they still help OEM defaults.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the title font class from the h1 up to the article__header
container so the byline and feed name inherit the same font. The
titleFollowsBodyFont toggle now controls all three elements together.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Align the displayed version with the release tag format (v2.0) and add a
button that hits the GitHub releases API; if the latest tag is newer the
release page opens, otherwise a snackbar reports the status.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New "Apply article font to whole app" toggle under Display & Appearance
  reuses the article font selection (including bundled and custom fonts)
  for MaterialTheme typography across the entire app.
- Article list now shows a small scroll-to-top FAB in the bottom right
  once the list has been scrolled.
- Bump versionName to 2.2 ahead of the v2.2 release.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The AnimatedVisibility check held a stale LazyListState reference because
`remember { derivedStateOf { ... } }` was not keyed. When the article list
transitioned from empty to populated, a new LazyListState was produced but
the derived state kept observing the original (empty) instance, so the FAB
never became visible after scrolling.

Keying `remember` on `listState` rebuilds the derived state when the
reference changes. Also bumps versionName to 2.2.1 for the patch release.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
transactionWithErrorHandling now returns Result<Unit> so callers
can observe transaction failures instead of silently treating them
as success. OPMLImportWorker returns Result.failure() on exception
so WorkManager records the failure. Bumps version to 2.2.2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pull-to-refresh indicator and the drawer refresh button could remain
stuck "refreshing" forever when a refresh job was cancelled (e.g. by
starting another refresh) or when the sync returned a failure. The old
flow only reset state inside the success path of the coroutine body, and
used invokeOnCompletion for refreshingAll — neither ran if the body
threw or was cancelled mid-flight. Wrap the refresh coroutine in
try/finally so the state-clearing callback always runs, and fold the
refreshingAll reset back into the same callback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers deploy, emulator, screen, and docs subcommands so future
build/test workflows can use the CLI where it complements Gradle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Some CDNs (e.g. img2.jintiankansha.me, used by WeChat/Kindle4RSS feeds)
apply hotlink protection that returns 403 when the Referer points to an
unrelated host. PR jocmp#1879 always set Referer to the article URL, which
tripped these checks and broke images for feeds like
feedmaker.kindle4rss.com.

For media sub-resources, send the request URL's own origin as Referer
instead. That still satisfies CDNs that simply require some Referer
(the jocmp#1878 need), passes same-origin hotlink checks, and avoids
leaking the article URL to third-party image hosts. Iframe and CORS
proxies keep the article URL as Referer since embeds can depend on it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-1776690802786

Add Claude Code GitHub Workflow
- Reword KDoc to clarify refererFor can return null (Issue jocmp#1878 is only
  satisfied when pageUrl is available, not unconditionally)
- Fix originOf to use uri.host + uri.port instead of uri.authority to
  prevent leaking user-info credentials into the Referer header

Co-authored-by: Shengqiang Zhang <Shengqiang-Zhang@users.noreply.github.com>
fix: Use image origin as Referer for proxied media requests
The workflow was copied from a Flutter repo and still instructed Claude to
run `ego_sdk/flutter/bin/flutter analyze|test`, which does not exist here.
Swap in this project's actual checks — `./gradlew :<module>:testDebugUnitTest`,
`./gradlew assembleFreeDebug`, and `make` / `make check` for JS/Liquid
template changes — and update the allowed-tools list accordingly so the
loop can actually verify its fixes.
The Claude auto-fix prompt still referenced `ego_sdk/flutter/bin/flutter`
commands inherited from the source repo. Swap those for this project's
Gradle/make commands (assembleFreeDebug, module unit tests, `make` /
`make check` for JS/Liquid assets) and update the allowed-tools list
accordingly so the Copilot review-fix loop actually runs the right
checks on capyreader PRs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Feeds like AI_era.weixin.xml render 18+ images per article, all lazy
beyond the first. Two failure modes could leave images stuck at the
opacity:0 default:

- WebView occasionally completes an image's network fetch without
  firing `load` or `error` (cache hits and lazy-loaded images), so the
  `.loaded` class was never added. Poll `img.complete` every 500ms for
  up to 30s and flip any resolved images visible; force-mark anything
  still pending at the deadline so broken images fall back to the
  browser's broken-image rendering instead of blank space. Also switch
  to `addEventListener` so the handler isn't clobbered if another
  script assigns onload/onerror.
- `proxyRequest` only had one try/catch covering the whole flow. If an
  exception occurred after `execute()` returned, the OkHttp Response
  was never closed, leaking connections back to the pool. With
  maxRequestsPerHost=5 and 18 images on the same host, subsequent
  requests could stall. Split into two try blocks and explicitly close
  the Response on the response-processing failure path. Also pass null
  as the encoding for non-text MIME types, log the failing URL, and
  skip the default UTF-8 charset for binary image bodies.

Also repoint tsconfig.json include paths from the non-existent
capy/src/main/assets/ location to the real app/src/main/assets/ one
so `make check` actually validates the JS assets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…IME types

Expand the UTF-8 default to cover application/json, application/javascript,
application/ecmascript, application/xml, and application/*+xml / *+json types,
not just text/* — matching WebView expectations for proxied textual responses.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The auto-fix step had no timeout, so when Claude invoked `./gradlew
assembleFreeDebug` on a bare `ubuntu-latest` runner (no Android SDK,
cold Gradle cache) the action stalled until GitHub's 6-hour job
ceiling. Observed on PR #3: the first review arrived at 08:29Z, the
Claude step started at 08:41Z, and was still running an hour later
with no progress.

Changes:
- Add `timeout-minutes: 30` to the Claude step so hangs self-resolve.
- Update the prompt to explicitly forbid full Gradle builds in this
  job and defer Kotlin build verification to the existing `test.yml`
  CI workflow. JS/Liquid `make` checks stay in-scope.
- Drop `Bash(./gradlew:*)` from the allowed-tools list so Claude
  can't regress into invoking it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Handle null OkHttp response body explicitly (close response and return
  null) instead of relying on NPE caught by the surrounding try/catch
- Hoist isTextMimeType's set literal to a companion object val to avoid
  per-call allocation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…prompt

- Use { once: true } on load/error listeners in setupImageLoadHandler so
  duplicate calls before image resolves do not register stacked handlers
- Remove stale ./gradlew hint from workflow prompt; Bash(./gradlew:*) is
  not in claude_args so the suggestion was misleading

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use { once: true } on load/error event listeners in setupImageLoadHandler
so they auto-remove after firing, preventing duplicate handlers if the
function is called more than once for the same image before it resolves.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…concile

fix(webview): reconcile image load state for WeChat feeds
… + auth

Introduce the `web/` top-level directory for a browser-based Capy Reader
client that syncs with Android via a shared Miniflux server. Phase 1 of
the plan in docs: Vite + React 18 + TypeScript shell with Tailwind, a
small in-repo component kit (Button, Input, Card, Label) styled for a
polished Inoreader-like UI, a custom-font pipeline sourced from the
Android `app/src/main/res/font/*.ttf` assets, a typed Miniflux API
wrapper + TanStack Query hooks, an auth context persisting the API
token in localStorage, and a login route that verifies the token via
`GET /v1/me` before signing the user in.

No Android code is touched; cross-device sync is enabled by pointing
both clients at the same Miniflux instance.

Verified: pnpm typecheck, pnpm test (5 passing), pnpm build succeed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Shengqiang-Zhang and others added 28 commits May 11, 2026 12:59
Previously, marking entries above as read kept the scroll container's
scrollTop unchanged while the unread-filtered list shrank, so the
viewport ended up past the anchor row (or empty) and the user had to
scroll back to the top to find unread articles.

Track the clicked row's entry id when invoking the action and
re-anchor the virtualizer to that entry on each entries update
(optimistic + refetch), then forget the anchor after a short delay
so unrelated future updates don't yank the scroll position.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gate the re-anchor scroll on the entries list actually shrinking below
its pre-action length, so the All-articles filter no longer jumps the
viewport when "mark above as read" is used there.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Track the anchor article's initial index instead of the list length to
decide when re-anchoring is needed after "mark above as read". The
previous length-based guard failed when the unread list was at the page
cap (100 entries): the refetch could return 100 entries again (older
items filling in), so the length never shrank, but the anchor article
had already moved upward and the scroll correction was skipped. Using
the index correctly handles both the partial-page case (fewer entries,
anchor moves earlier) and the full-page case (same entry count, but
anchor index is lower), while still avoiding spurious jumps in the
All-articles filter where the anchor index stays unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clear markAboveAnchorId immediately when entries refresh but the anchor
index is unchanged (All-articles filter), preventing a stale scroll if a
later refetch or filter change happens to move the row upward. Guard the
first effect run (before refetch) with an entries-reference comparison so
the unread-filter scroll path is still reached after the real refresh.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The optimistic cache update in useMarkEntriesAsRead creates a new entries
reference with statuses changed but all entries still present, so the anchor
stays at the same index. The previous guard cleared markAboveAnchorId on
that first post-initial change, dropping the anchor before the real network
refetch returned the shrunken unread list.

Track how many entries changes have occurred since the anchor was set; only
clear the anchor in the same-index case on the second change (the real
refetch) so the Unread-filter scroll correction still fires.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the change-counter guard (>= 2) with a timeout-based cleanup.
In the All filter, the optimistic update makes the cache match the server
response, so React Query structurally shares the refetched array and
never delivers a second entries reference — leaving markAboveAnchorId
armed indefinitely. The timeout fires 3 s after the same-index entries
change and clears the anchor; in the Unread filter the real refetch
arrives first and its effect cleanup cancels the timer before it fires.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-after-mark-above

fix(web): scroll to anchor article after "mark above as read"
Adds a small Copy icon button next to each feed entry that copies the
feed_url to the clipboard, swapping to a Check icon for ~1.5s as visual
confirmation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(web): copy-to-clipboard icon for feed URLs in Manage Feeds
Previously the scroll-to-top after refresh was gated on
markReadOnScrollEnabled, so users with that setting off would stay at
their prior scroll position and miss the newly-fetched articles at the
top. Always jump to the top once a pull-to-refresh completes so the
newest items are visible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…p-after-refresh

fix(android): scroll article list to top after pull-to-refresh
…alog

Adds a per-row category dropdown to the Manage feeds dialog. Selecting a
different category sends PUT /v1/feeds/{id} with the new category_id and
invalidates feeds/categories/counters/entries on success.

Per-feed pending state is tracked in a Set so concurrent moves on
different rows lock independently; finishing one move never re-enables
another row that's still in flight.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Single-entry status changes (auto-mark on click, the read/unread toggle
in ArticleView, and the m keyboard shortcut) used to invalidate the
entries query, which refetched ?status=unread from Miniflux and made
the just-read row vanish from the list.

useUpdateEntryStatus now skips the entries invalidation. The optimistic
onMutate already flips the row's status in the cached list, so it
re-renders with read styling but stays in place. Counters and the
per-entry cache still invalidate.

Bulk "Mark above as read" (useMarkEntriesAsRead) keeps its entries
invalidation, which is the explicit way to clear out read items above
the current article. Refresh button and window-focus refetch behave
unchanged.

Tests pin both behaviors: useUpdateEntryStatus does not invalidate
["entries"], useMarkEntriesAsRead does.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…icle list

Mirror the Android app's favicon treatment in the web companion. Sidebar
feed rows and article list rows now render each feed's Miniflux-cached
favicon (via /v1/icons/{id}, returned as a base64 data URL) with a
first-letter fallback when no icon has been fetched yet.

Each feed also gets a deterministic soft background tint. The palette is
built by spreading hues evenly inside each category and rotating the
starting hue per category by the golden angle, so feeds within the same
category stay visually distinct even when the global hue wheel
inevitably reuses hues across hundreds of feeds. The same feed colour
appears in both columns so an article's source is recognisable at a
glance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… fresh

When useUpdateEntryStatus marks an entry as unread, the Unread list cache
must be invalidated so the article appears there even if it was not
already cached. The previous change skipped all entries invalidation, but
that breaks the mark-unread path.

Now only the mark-read path skips invalidation (keeping the article
visible in-place during the reading session). Mark-unread still
invalidates so newly-unread articles are fetched into the Unread view.

Add a test that pins the mark-unread invalidation contract.

Co-authored-by: Shengqiang Zhang <Shengqiang-Zhang@users.noreply.github.com>
main (PR #18-#20) added Copy URL functionality to ManageFeedsDialog
(Check/Copy icons, copiedFeedId state, useEffect cleanup, handleCopyFeedUrl).
This branch added the category move dropdown (useUpdateFeed, movingFeedIds,
handleMoveFeed, <select>). Manually merge both so no changes are lost.

Feed rows now show: feed info | category selector | copy-URL button | delete button.

Co-authored-by: Shengqiang Zhang <Shengqiang-Zhang@users.noreply.github.com>
Commit 924667e manually duplicated the copy-URL feature from main into
this branch, causing a 3-way merge conflict: both main and this branch
independently added code in the same file region (around the delete
button). Removing it lets GitHub's merge cleanly combine copy-URL from
main with move-feeds from this branch.

Co-authored-by: Shengqiang Zhang <Shengqiang-Zhang@users.noreply.github.com>
…ints

feat(web): real feed favicons + per-feed colour tint in sidebar and article list
…d-keep-unread-on-click

# Conflicts:
#	web/src/features/subscriptions/ManageFeedsDialog.tsx
When marking an article read, onMutate had already optimistically
rewritten all cached ['entries'] lists (including inactive Unread
caches). The prior code skipped all invalidation, so a cached Unread
list could show a read article for up to 10 s if the user switched
back within the stale window.

Now we call removeQueries with type:"inactive" for the read case,
which evicts any inactive entry caches while leaving the active list
untouched — preserving the "article stays visible during reading
session" UX without leaking stale data into other views.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace removeQueries({ type: "inactive" }) with invalidateQueries({
refetchType: "none" }) when marking an entry read. This marks all
["entries"] caches stale without triggering an immediate refetch, so:
- The active list keeps its optimistic update visible during the
  current reading session.
- Both active and inactive Unread caches will refetch fresh data the
  next time they are observed, preventing a read article from
  re-appearing after navigation within the 10 s stale window.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The test incorrectly expected no ["entries"] invalidation when marking
read, but onSettled calls invalidateQueries with refetchType:"none" to
mark entries stale without triggering a refetch. Update the assertion to
verify the no-refetch behaviour instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…focus refetch

Switch from invalidateQueries({refetchType:"none"}) to removeQueries({type:"inactive"})
when marking an entry read. The previous approach marked the active Unread query stale,
so refetchOnWindowFocus would refetch it and remove the article mid-session. Evicting
only inactive caches leaves the active list untouched while still ensuring stale caches
are cleaned up when the user navigates away.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The seeded entries query had no active observer, so onSettled's
removeQueries({ type: "inactive" }) evicted it before the assertion
ran. Added a QueryObserver subscription to simulate an active list
view, matching the real-app scenario where a component observes the
query.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The QueryObserver was created with `enabled: false`, but TanStack Query's
isActive() check ignores observers whose enabled resolves to false. As a
result, removeQueries({ queryKey: ["entries"], type: "inactive" }) in
useUpdateEntryStatus.onSettled was evicting the seeded unread cache
before the assertion could read it, which broke the deploy preview CI.

Replace with an enabled observer that has a no-op queryFn returning the
seed and staleTime: Infinity, so the query is genuinely active and the
seeded/optimistic data is preserved through settlement.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…eep-unread-on-click

feat(web): move feeds between categories; keep articles in Unread after click
Bring in 94 upstream commits through 0e42752 (incremental Miniflux save).
Highlights: M3 Expressive toolbar, sync outbox for article statuses,
SQLite cursor window reduction, mercury-parser-kt for full content,
mark-read-on-scroll fixes, and many translations.

For the 6 conflicting files, kept the local version to preserve fork
changes (unread cache, web feed move/keep-unread-on-click, .claude/worktrees
gitignore, version line, tsconfig).

Conflicts resolved with --ours:
- .gitignore
- app/build.gradle.kts
- app/src/main/java/com/capyreader/app/ui/articles/ArticleScreen.kt
- app/src/test/java/com/capyreader/app/ui/articles/ArticleScreenViewModelTest.kt
- capy/src/main/java/com/jocmp/capy/accounts/local/LocalAccountDelegate.kt
- tsconfig.json
@Shengqiang-Zhang
Copy link
Copy Markdown
Author

Opened against the wrong repo by mistake — this should land on the fork (Shengqiang-Zhang/capyreader), not upstream. Closing. Apologies for the noise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant