* main:
fix(deps): updte all dependencies
fix(ci): use same-repository reference syntax for actions
fix: disable SQL logging in release builds
feat: convert more inefficient queries
feat: optimize persisted post state
fix: hide logout for anonymous users
fix: make settings screen scrollable
fix(deps): update sentry
fix(deps): update agp to v9.3.1
- Continues the work of replacing unnecessarily reactive queries
- Pushed down some more sorting work into SQL
- Spell out columns in queries that were doing full row scans when
not required.
- Read posts are an append only list, so instead of re-fetching it from the database
every time it is now cached on repository init and then subsequent `markRead` calls
just manually update the set and avoid the unnecessary trip through SQLite.
- Since ReadPosts will grow forever in the current set up, I've optimised the table for
on disk space savings by creating it without ROWID. In a local test with 100K rows the
difference between no ROWID and the default was about 1.27 MB versus 3.08 MB. I am not
at 100K yet AFAICT, but this is still just a nice thing to have under my belt.
- Splits out more focused queries for SavedPosts table since re-fetching every column
to extract a field or every row to do a count is pretty wasteful.
- There is also now an index for SavedPosts with createdAt and shortId fields since they
are fetched the most often.
- A lot of queries were unnecessarily going through Flow-based APIs when they were used
for oneshot operations, those are now direct calls.
- Foreign key relations can cause `INSERT OR REPLACE` to accidentally delete a different
table's row which I don't use yet, but I found the `ON CONFLICT DO UPDATE SET` that
deals with it so I've adopted it pre-emptively so future me can be confused and dig up
this commit message.
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [actions/setup-java](https://redirect.github.com/actions/setup-java) |
action | minor | `v5.5.0` → `v5.6.0` |
---
### Release Notes
<details>
<summary>actions/setup-java (actions/setup-java)</summary>
###
[`v5.6.0`](https://redirect.github.com/actions/setup-java/compare/v5.5.0...v5.6.0)
[Compare
Source](https://redirect.github.com/actions/setup-java/compare/v5.5.0...v5.6.0)
</details>
---
### Configuration
📅 **Schedule**: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Enabled.
♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box
---
This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNjUuMSIsInVwZGF0ZWRJblZlciI6IjQzLjI2NS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This is rather useless in the current form since it doesn't actually update the fixtures so the tests don't do anything useful yet. Will reintroduce it at a later point.
The 9.4.x R8 series seems to have a verifier bug causing the comments page to trigger a crash.
This is fixed in https://issuetracker.google.com/issues/530633707 but there is no version out
with this included.
Fixes COMPOSE-LOBSTERS-BR
Replaces the Kspoon-based parsing via Retrofit into a lower level
version based on Ksoup directly, implemented in a Zipline module that
can be updated independently of the app itself.
The primary motivation is to be able to update the HTML scraper
independent of the app since the site will likely to continue having
changes in the markup that break my parsers, and cutting emergency
releases is tiresome.
~~This currently doesn't work due to a `stack overflow` error from
QuickJS, but it did work *briefly* before I managed to break it and
never got it working again.~~
Managed to fix the problems through multiple changes
- Improve code size by removing JSON as an intermediary between the
Zipline-defined API and the Android app, instead using a simpler text
based format that's just a straight concatenation. This let us drop
`kotlinx-serialization-json` from the module dependencies.
- Once the code size was under control we started seeing crashes because
we were violating JS threading invariants where JS loading and JS
execution needed to happen on the same thread. This was resolved using
the `DispatcherConfinedLobstersParserService` wrapper that forces all
parser method calls to run through the same Dispatcher as
`ZiplineLoader`.
Why this change exists:
- The real problem was not just missing serializers; it was that the Zipline parser boundary was unstable in actual Android runtime loading.
- The goal of this change is to make the parser service boundary reliable without collapsing it into app model types or otherwise defeating the parser/API split.
- The important outcome is that Android can load and use the embedded parser consistently in the verified runtime path.
What this change guarantees:
- parser-specific boundary types remain supported explicitly
- embedded parser loading follows the stable path used in debug verification
- the previously observed runtime failures are removed from the validated path
Verification:
- build-brief ./gradlew :zipline-parser-api:jvmTest :zipline-parser:jvmTest :zipline-parser:compileProductionExecutableKotlinJsZipline :android:installDebug
- Emulator launch on emulator-5554: no missing LobstersPost serializer, no SIGSEGV/Scudo crash, no QuickJs stack overflow in final run, posts loaded
Replace the old Retrofit HTML converters with a Zipline-backed parser
client and a Retrofit converter that delegates HTML decoding to the
parser module. Update the app and tests to consume the new parsed API
results.
Introduce a dedicated zipline-parser module for Lobsters HTML parsing,
upgrade Zipline to a compatible release, and add the generated API
dump plus a helper script for deploying parser artifacts.
Move the shared parser-facing contracts into commonMain and keep the
JVM-only database mappers in jvmMain. This separates shared model
types from platform-specific storage code and prepares the branch for
a dedicated parser module.
Thanks for asking me to work on this. I will get started on it and keep
this PR's description up to date as I form a plan and make progress.
> ----
>
> *This section details on the original issue you should resolve*
>
> <issue_title>1.65.0 Certificate Pinning error </issue_title>
> <issue_description>Opening the App just gives the error
>
> > Certificate pinning failure
>
> I imagine that this is due to the new certificate on lobster:
>
> > Not Before Tue, 02 Jun 2026 03:17:13 GMT
>
>
> backtrace:
> ```
> javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
> Peer certificate chain:
> sha256/nmaozNNuQ1MLbF/avDVhKPEu1JiZibqa/rcIhF6rJaU=: CN=lobste.rs
> sha256/brzvtCELCIZUo4sD/qPX0ccRtPsd3DY6RfmxpOU9oB4=: CN=YE1,O=Let's
Encrypt,C=US
> sha256/sCkq5UWXjg+7mKu9lMhhYF5bGLsy7VI/UNW3tccdR7w=: CN=Root
YE,O=ISRG,C=US
> sha256/diGVwiVYbubAI3RW4hB9xU8e/CH2GnkuvVFZE8zmgzI=: CN=ISRG Root
X2,O=Internet Security Research Group,C=US
> Pinned certificates for lobste.rs:
> sha256/Bla1TIdpGeHXQS0/CIrA5hhFhOTZd94IIJRS3G3AcIo=
> sha256/jQJTbIh0grw0/1TkHSumWb+Fs0Ggogr621gT3PvPKG0=
> sha256/C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=
> at
okhttp3.CertificatePinner.check$okhttp(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:427)
> at
okhttp3.internal.connection.ConnectPlan.connectTls(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:223)
> at
okhttp3.internal.connection.ConnectPlan.connectTlsEtc(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:153)
> at
okhttp3.internal.connection.FastFallbackExchangeFinder.find(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:139)
> at
okhttp3.internal.connection.ConnectInterceptor.intercept(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:30)
> at
okhttp3.internal.http.RealInterceptorChain.proceed(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:104)
> at
okhttp3.internal.cache.CacheInterceptor.intercept(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:1872)
> at
okhttp3.internal.http.RealInterceptorChain.proceed(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:104)
> at
okhttp3.internal.cache.CacheInterceptor.intercept(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:666)
> at
okhttp3.internal.http.RealInterceptorChain.proceed(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:104)
> at
okhttp3.internal.cache.CacheInterceptor.intercept(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:220)
> at
okhttp3.internal.http.RealInterceptorChain.proceed(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:104)
> at
io.sentry.okhttp.SentryOkHttpInterceptor.intercept(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:461)
> at
okhttp3.internal.http.RealInterceptorChain.proceed(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:104)
> at
okhttp3.logging.HttpLoggingInterceptor.intercept(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:576)
> at
okhttp3.internal.http.RealInterceptorChain.proceed(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:104)
> at
okhttp3.internal.cache.CacheInterceptor.intercept(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:773)
> at
okhttp3.internal.http.RealInterceptorChain.proceed(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:104)
> at
dev.msfjarvis.claw.core.network.UserAgentInterceptor.intercept(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:19)
> at
okhttp3.internal.http.RealInterceptorChain.proceed(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:104)
> at
dev.msfjarvis.claw.core.network.RetryAfterInterceptor.intercept(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:3)
> at
okhttp3.internal.http.RealInterceptorChain.proceed(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:104)
> at
okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:118)
> at
okhttp3.internal.connection.RealCall$AsyncCall.run(r8-map-id-4720644bb8de639be45bbb06d58eed30b6402544fe3e08afbfc1a8ff6d7ebbc6:42)
> at
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1154)
> at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:652)
> at java.lang.Thread.run(Thread.java:1563)
> Suppressed: javax.net.ssl.SSLPeerUnverifiedException: Certificate
pinning failure!
> Peer certificate chain:
> sha256/nmaozNNuQ1MLbF/avDVhKPEu1JiZibqa/rcIhF6rJaU=: CN=lobste.rs
> sha256/brzvtCELCIZUo4sD/qPX0ccRtPsd3DY6RfmxpOU9oB4=: CN=YE1,O=Let's
Encrypt,C=US
> sha256/sCkq5UWXjg+7mKu9lMhhYF5bGLsy7VI/UNW3tccdR7w=: CN=Root
YE,O=ISRG,C=US
> sha256/diGVwiVYbubAI3RW4hB9xU8e/CH2GnkuvVFZE8zmgzI=: CN=ISRG Root
X2,O=Internet Security Research Group,C=US
> Pinned certificates for lobste.rs:
> sha256/Bla1TIdpGeHXQS0/CIrA5hhFhOTZd94IIJRS3G3AcIo=
> sha256/jQJTbIh0grw0/1TkHSumWb+Fs0Ggogr621gT3PvPKG0=
> sha256/C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=
> ... 27 more
> ```
>
>
> Version: 1.65.0
> </issue_description>
>
> <agent_instructions>Remove the certificate pinning
logic</agent_instructions>
>
> ## Comments on the Issue (you are @claude[agent] in this section)
>
> <comments>
> </comments>
---------
Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com>
Co-authored-by: msfjarvis <13348378+msfjarvis@users.noreply.github.com>
This will be reverted back after the release
Revert "feat: re-do comment UI to look like Boost for Reddit"
This reverts commit 03e0a13e1d.
Revert "build: add compose screenshot testing support"
This reverts commit 5992a5fa15.
Revert "feat: rework comment entry layout and enable upvote action"
This reverts commit 4904c76220.
Revert "feat: re-do comment upvote UI"
This reverts commit c32ed849d2.
Only persist Lobsters login cookies after the WebView proves the
session is authenticated, and treat anonymous lobster_trap cookies as
logged out state. Also clear the WebView cookie jar on logout so the
embedded login flow cannot silently restore a prior session.
Simplify comment timestamps to match the current site markup by
tracking one timestamp plus an edited flag instead of fabricating a
creation time we no longer receive.
This PR contains the following updates:
| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [dev.zacsweers.metro](https://redirect.github.com/ZacSweers/metro) |
`1.0.0-RC2` → `1.0.0-RC3` |

|

|
|
[dev.zacsweers.metro:metrox-viewmodel-compose](https://redirect.github.com/ZacSweers/metro)
| `1.0.0-RC2` → `1.0.0-RC3` |

|

|
|
[dev.zacsweers.metro:metrox-viewmodel](https://redirect.github.com/ZacSweers/metro)
| `1.0.0-RC2` → `1.0.0-RC3` |

|

|
|
[dev.zacsweers.metro:metrox-android](https://redirect.github.com/ZacSweers/metro)
| `1.0.0-RC2` → `1.0.0-RC3` |

|

|
---
> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/266) for more information.
---
### Release Notes
<details>
<summary>ZacSweers/metro (dev.zacsweers.metro)</summary>
###
[`v1.0.0-RC3`](https://redirect.github.com/ZacSweers/metro/blob/HEAD/CHANGELOG.md#100-RC3)
[Compare
Source](https://redirect.github.com/ZacSweers/metro/compare/1.0.0-RC2...1.0.0-RC3)
*2026-04-23*
This is the third release candidate for Metro 1.0!
This means that its *runtime* APIs (`runtime`, `metrox` artifacts,
Gradle plugin, etc) will be API stable unless annotated with an
experimental annotation.
*Changes since RC2*
##### `enableFunctionProviders` enabled by default
This release promotes `enableFunctionProviders` (i.e. `() -> T` syntax
for providers) to stable, enables it by default, and marks usage of
Metro's native `Provider` as a warning.
This may require some migration in existing codebases. To help with
this, there's a [new section in
docs](https://zacsweers.github.io/metro/latest/adoption/#migrating-providert-to-function-syntax)
with information as well as a helper script: adoption docs guidance here
too.
##### New
- Add a new `desugaredProviderSeverity` option (default: `WARN`) that
reports a diagnostic when `Provider<T>` is used instead of the preferred
`() -> T` form. Set this to `NONE` to disable the warning during
migration, or `ERROR` to enforce the new style. Automatically treated as
`NONE` when `enableFunctionProviders` is disabled.
- **\[FIR]** Add diagnostic checks against providing intrinsic types
(`Provider`, `Lazy`, etc.) from `@Provides` declarations.
- **\[Gradle]** Introduce a new `compilerOptions {}` DSL for free Metro
compiler options and flags.
- **\[Gradle]** Add `IDE_WARN` and `IDE_ERROR` members to
`DiagnosticSeverity` to allow configuring some diagnostics to *only* run
in IDE sessions. Useful for diagnostics you only want to surface to
readers in the IDE without emitting compiler warnings in real (CLI)
compilations.
##### Enhancements
- **\[FIR]** Add a new diagnostic for ambiguous inject constructors.
Namely, cases where a class is annotated with `@Inject`, defines a
secondary constructor but no primary constructor. This is ambiguous,
metro now asks you to pick a lane.
- **\[FIR]** Add a new diagnostic for check against `private`
contributions.
- **\[FIR]** When rendering diagnostics in the IDE, use short names for
classes since they are shown in context already.
- **\[JVM/JS]** Generate `@JvmStatic` and `@JsStatic` annotations onto
static-ish functions for better staticization on those platforms.
- **\[docs]** Migrate doc site to Zensical. Works the same, fresh-ish
coat of paint!
##### Fixes
- **\[FIR]** Fix not recognizing `FirDeclarationOrigin.Precompiled`
origins when checking resolved default binding types. Previously we only
considered `FirDeclarationOrigin.Library`, but incremental compilation
uses `FirDeclarationOrigin.Precompiled` to differentiate. This would
result in misreads of default binding types in some cases during IC.
- **\[FIR]** When merging `@ContributesTo` types to graph supertypes,
add the original interface as well. This ensures they are visible in
ObjC exports, as the metro-generated contribution interfaces are
normally excluded.
- **\[IR]** Fix secondary inject constructors support when
`generateContributionProviders` is enabled.
- **\[IR]** Fix implicit class key lookup for map keys on
source-declared `@Binds` declarations.
- **\[IR]** Fix `implementsProviderType()` check in the compiler to only
exactly match `Function0` types when `enableFunctionProviders` is
enabled.
- **\[IR]** Set `thisGraphInstance` field types as the graph impl type
to avoid a Wasm issue.
- **\[interop]** Fix `@ContributesSubcomponent.Factory` interop with
square/anvil.
- **\[interop]** Fix `@MergeSubcomponent.Factory` interop with
zacsweers/anvil (anvil-ksp).
- **\[docs]** Fix source links in Dokka API docs.
- **\[docs]** Don't publish `**.internal.**` APIs in Dokka API docs.
##### Changes
- `enableFunctionProviders` (i.e. `() -> T` syntax for providers) is now
enabled by default. Previously this required opting in. The
function-syntax form is now the **recommended** way to declare provider
dependencies; `Provider<T>` is still supported but treated as a
desugared alternative and a **warning** by default, similar to if you
were to use `Function0` instead of `() -> T` syntax for functions. See
the [metro-intrinsics](docs/metro-intrinsics.md) docs for more details.
- **\[IR]** Remove deprecated `indexInOldValueParameters` use in IR for
better `2.4.0`+ support.
- **\[Gradle]** Promote `enableFunctionProviders` to stable.
- **\[Gradle]** Remove deprecated `useAssistedParamNamesAsIdentifiers`
property.
- **\[Gradle]** Remove `deduplicateInjectedParams` property.
- **\[Gradle]** Remove `enableKlibParamsCheck` property, use the new
`compilerOptions` API.
- **\[Gradle]** Remove `enableFullBindingGraphValidation` property, use
the new `compilerOptions` API.
- **\[Gradle]** Remove `enableGraphImplClassAsReturnType` property, use
the new `compilerOptions` API.
- **\[Gradle]** Remove `shrinkUnusedBindings` property, use the new
`compilerOptions` API.
- **\[metrox-android]** Change `MetroAppComponentProviders` accessor
multibindings to expose function types instead of `Provider` types.
- **\[metrox-viewmodel]** Change `MetroViewModelFactory` and
`MetroViewModelMultibindings` accessor multibindings to expose function
types instead of `Provider` types.
- Support Kotlin `2.4.0-Beta2`.
- Update embedded androidx.tracing to `2.0.0-alpha06`.
##### Contributors
Special thanks to the following contributors for contributing to this
release!
- [@​anddani](https://redirect.github.com/anddani)
- [@​jonamireh](https://redirect.github.com/jonamireh)
</details>
---
### Configuration
📅 **Schedule**: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Enabled.
♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box
---
This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMzkuNyIsInVwZGF0ZWRJblZlciI6IjQzLjEzOS43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
---------
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
|
[actions/upload-artifact](https://redirect.github.com/actions/upload-artifact)
| action | patch | `v7.0.0` → `v7.0.1` |
---
> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/266) for more information.
---
### Release Notes
<details>
<summary>actions/upload-artifact (actions/upload-artifact)</summary>
###
[`v7.0.1`](https://redirect.github.com/actions/upload-artifact/compare/v7.0.0...v7.0.1)
[Compare
Source](https://redirect.github.com/actions/upload-artifact/compare/v7.0.0...v7.0.1)
</details>
---
### Configuration
📅 **Schedule**: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Enabled.
♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box
---
This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMTAuMiIsInVwZGF0ZWRJblZlciI6IjQzLjExMC4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
`benchmark` was hardcoding `compileSdk` because it applies
`com.android.test` + `kotlin-android` — neither of which pulls in
`AndroidCommonPlugin`. The SDK values were also duplicated across two
plugin files with no shared constant.
## Changes
- **New `AndroidConstants.kt`** — single source of truth for
`COMPILE_SDK = 37` and `TARGET_SDK = 37`
- **`AndroidCommonPlugin`** — replace literal `37` with `COMPILE_SDK`
- **`ApplicationPlugin`** — replace literal `37` with `TARGET_SDK`
- **`benchmark/build.gradle.kts`** — apply
`dev.msfjarvis.claw.android-common` so `compileSdk` is set by the
plugin; drop the hardcoded value. `TestExtension` implements
`CommonExtension`, so the plugin's `configure<CommonExtension>` block
applies cleanly.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: msfjarvis <13348378+msfjarvis@users.noreply.github.com>
Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
# Claw for [lobste.rs](https://lobste.rs) [](https://github.com/msfjarvis/compose-lobsters/actions/workflows/ci.yml)
Unofficial Android app for read-only access to [lobste.rs](https://lobste.rs), built with [Jetpack Compose](https://developer.android.com/jetpack/compose).
Unofficial Android app for the [lobste.rs](https://lobste.rs) linkblogging community, built with [Jetpack Compose](https://developer.android.com/jetpack/compose).
> [!WARNING]
> This app uses a JSON API that Lobsters does not expose by choice and [the maintainer has no interest in having](https://github.com/lobsters/lobsters/issues/1663#issuecomment-3074472781), so it may break in any manner at any point in time. File an issue [here](https://github.com/msfjarvis/compose-lobsters/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen) if you come across any bugs, do not bother the upstream developers about a feature they don't support.
## Features
- Browse posts in the frontpage (Hottest) and Newest feeds
message="`KeepUntilTransitionsFinished` is defined both as a member in class `androidx.compose.animation.ExitTransition.Companion` and an extension in package `androidx.compose.animation`. The defined behavior for this is to use the member, but since the extension is explicitly imported into this file, there's a chance that this was not expected. (One common way this happens is for members to be added to a class after code was already written to use an extension)."
{"username":"msfjarvis","created_at":"2020-04-24T11:41:56.000-05:00","is_admin":false,"about":"Android and Kotlin developer, currently working for [Dyte](https://dyte.io/)","is_moderator":false,"karma":1343,"avatar_url":"/avatars/msfjarvis-100.png","invited_by_user":"Amolith","github_username":"msfjarvis"}
<p>There are often cases where comments with a poor score (say, 1 point), invite high-quality replies that receive many upvotes.</p>
<p>These threads are not shown at the top of the story's comments page because their "root comment" has a low score.</p>
<p>An example, as of 2022-07-22 17:00 UTC, is <ahref="https://lobste.rs/s/ekvqcf/random_wallpaper_with_just_bash_systemd#c_vop9bt"rel="ugc">https://lobste.rs/s/ekvqcf/random_wallpaper_with_just_bash_systemd#c_vop9bt</a>:</p>
<ul>
<li>the root comment has 1 point,</li>
<li>the first reply has 12 points,</li>
<li>and, in total, 18 points have been accumulated by all the replies with score > 1 point.</li>
</ul>
<p>Because the root comment has only 1 point, it remains at the bottom of the page, bringing down with itself also the good comment with 12 points and all the other upvoted comments.</p>
<p>Could an alternative ranking system be put in place, where the threads and subthreads are not sorted by the score of their root comment, but by the <em>sum of all the scores of their children</em>?</p>
<p>(Maybe taking into account only children with <code>>1</code> point, to avoid giving importance to superficial back-and-forth discussions.)</p>
<p>to avoid giving importance to superficial back-and-forth discussions</p>
</blockquote>
<p>Maybe take the max, instead of the sum? That way it's still comparing the score of individual comments. A highly-upvoted child would rank higher than a less-upvoted toplevel comment, but long threads wouldn't get any bonus.</p>
<p>For better or worse, the deeper you go into threads the less likely it is that the discussion is still about the original submission. Your proposal, or the max variant presented elsewhere, I fear would unduly reward flamebait and off-topic discussion.</p>
<p>Your proposal, or the max variant presented elsewhere, I fear would unduly reward flamebait and off-topic discussion.</p>
</blockquote>
<p>Such off-topic discussions that stray away from the main topic do exist, are a nuisance, and, surely, they should not be rewarded with more attention. However, such sub-threads exists as a long string of short comments with no or very few upvotes. (I wonder if the data confirms this or it is just my impression.)</p>
<p>For this reason I believe that a) not counting in comments with score==1 or b) <ahref="https://lobste.rs/~dpercy"rel="ugc">@dpercy</a>'s max variant would avoid rewarding such discussions.</p>
<p>Just like to point out that the assumption here is that upvotes is a metric of quality. On lobste.rs as in any other social context, upvotes/support is a measure of popularity.</p>
<p>There are many instances where the two are correlated, and some important instances where they are not.</p>
<p>Just like to point out that the assumption here is that upvotes is a metric of quality. On lobste.rs as in any other social context, upvotes/support is a measure of popularity.</p>
</blockquote>
<p>I fully agree: upvotes are a metric of popularity and not of quality. But this proposal does not assume that.</p>
<p>Regardless of what upvotes represent, currently threads are sorted in descending order of (upvotes, time). All that this proposal suggests, is to change that to (sum(upvotes of root-and-children), time).</p>
<p>The only assumption here is: if ranking a thread by upvotes of its root comment is considered good (and at the moment it is), then ranking by the sum of the upvotes of the whole thread is better.</p>
<p>Tangential to your effort here, on a personal note, if I find a discussion interesting I read all the posts regardless of ranking. On the discussions I find worthwhile I find that post quality is unrelated to upvotes.</p>
<p>On some discussions I find the top voted comment has devolved into a long tail of niche discussion, often acrimonious, which is not as useful as later posts.</p>
<p>Worst to pick out of the noise are replies to replies which are great but buried in not so great comments.</p>
<p>I feel kind of honored to be taken as an example, so please let me share my personal thoughts (TL;DR below):</p>
<p>It is impossible to find <em>the</em> definite ranking system, but let me elaborate by using 3 extremes to see where the current and proposed systems fail:</p>
<ol>
<li>
<em>highly controversial root comment</em> with score 0 (100 upvotes, 100 downvotes), logically controversial responses with median 0,</li>
<li>
<em>root comment preaching to the choir</em> with logically high score and usually high-score responses,</li>
<li>
<em>low-score root comment with high-score responses</em> (like the presented case)</li>
<li>
<em>OC high-value root comment</em> with high score and high-value responses</li>
</ol>
<p>It all depends on personal preference though, but I personally would like more controversial topics to be on top and less controversial ones to be secondary. The reason for that is that you otherwise might end up with echo-chambers, given users are encouraged to preach to the choir for a high karma score. As a tangent, I would prefer two karmas for each user: A "paragon-karma" and "renegade-karma" whose sum would be an "influence" score (in the end up- or downvotes both reflect a certain influence) and whose difference would be a "non-controversiality-score" (a low difference reflects controversiality, a high score the opposite). All in all, maybe we should move away complete from the miriad of downvote-options on lobsters and simply have an "agree" and "disagree". If something is spam, you can report it, if something is incorrect, you can write a response rectifying it (which would then be subject to a vote of agreement and disagreement, depending on how well you state your case).</p>
<p>The proposal to add replies into the weight would not change the ranking of controversial topics (1), but even moreso weigh comments preaching to the choir (2) because everyone replying would aim to also get some karma. It would solve the presented case (3) though. Case (4) would also be favoured.</p>
<p>The proposal by <ahref="https://lobste.rs/~dpercy"rel="ugc">@dpercy</a> to the take the max would not change (1)'s ranking, probably not affect (2) but help (3) as well. (4) is also positively affected.</p>
<p>TL;DR, here's my proposal so more highly-controversial topics (those that are interesting) are more favoured: Consider root comment and replies as equals and only take <em>influence</em> (sum of up- and downvotes) as a score.</p>
<p>This benefits (1), which in the current form end up at the bottom, which makes zero sense. It also benefits (2) and (4), which is a forced compromise, given the score does not really show how "original" a comment is (this is why Reddit probably introduced awards to allow users to weigh very good posts). By taking all replies into account, it also, of course, benefits (3), which makes sense to push up.</p>
<p>Replies with no votes increase the influence of the thread by 1, indeed, but isn't the purpose of the score to show what the hivemind thinks? By scoring them as zero (which was proposed here), you effectively value an elaborated response lower than a simple upvote of the original comment. Instead, why not simply "fold" long subthreads so they don't take up too much space?</p>
<p>As another tangent: By weighing root and children, this might encourage people to respond to "deep" threads instead of writing a new post.</p>
<p>Let's see what <ahref="https://lobste.rs/~pushcx"rel="ugc">@pushcx</a> decides in the end. :)</p>
<p>I've moved most of the <ahref="https://github.com/lobsters/lobsters/blob/cf52ab9f3f0b3ac805effc2e718f15922cb67332/app/models/comment.rb#L340"rel="ugc">vote</a> into the db, but <ahref="https://github.com/lobsters/lobsters/blob/cf52ab9f3f0b3ac805effc2e718f15922cb67332/app/models/comment.rb#L219"rel="ugc">not all</a>. If <code>calculated_confidence</code> finished its move into the db almost any algorithm would be faster than the current implementation.</p>
<p>There are often cases where comments with a poor score (say, 1 point), invite high-quality replies that receive many upvotes.</p>
<p>These threads are not shown at the top of the story's comments page because their "root comment" has a low score.</p>
<p>An example, as of 2022-07-22 17:00 UTC, is <ahref="https://lobste.rs/s/ekvqcf/random_wallpaper_with_just_bash_systemd#c_vop9bt"rel="ugc">https://lobste.rs/s/ekvqcf/random_wallpaper_with_just_bash_systemd#c_vop9bt</a>:</p>
<ul>
<li>the root comment has 1 point,</li>
<li>the first reply has 12 points,</li>
<li>and, in total, 18 points have been accumulated by all the replies with score > 1 point.</li>
</ul>
<p>Because the root comment has only 1 point, it remains at the bottom of the page, bringing down with itself also the good comment with 12 points and all the other upvoted comments.</p>
<p>Could an alternative ranking system be put in place, where the threads and subthreads are not sorted by the score of their root comment, but by the <em>sum of all the scores of their children</em>?</p>
<p>(Maybe taking into account only children with <code>>1</code> point, to avoid giving importance to superficial back-and-forth discussions.)</p>
<p>to avoid giving importance to superficial back-and-forth discussions</p>
</blockquote>
<p>Maybe take the max, instead of the sum? That way it's still comparing the score of individual comments. A highly-upvoted child would rank higher than a less-upvoted toplevel comment, but long threads wouldn't get any bonus.</p>
<p>For better or worse, the deeper you go into threads the less likely it is that the discussion is still about the original submission. Your proposal, or the max variant presented elsewhere, I fear would unduly reward flamebait and off-topic discussion.</p>
<p>Your proposal, or the max variant presented elsewhere, I fear would unduly reward flamebait and off-topic discussion.</p>
</blockquote>
<p>Such off-topic discussions that stray away from the main topic do exist, are a nuisance, and, surely, they should not be rewarded with more attention. However, such sub-threads exists as a long string of short comments with no or very few upvotes. (I wonder if the data confirms this or it is just my impression.)</p>
<p>For this reason I believe that a) not counting in comments with score==1 or b) <ahref="https://lobste.rs/~dpercy"rel="ugc">@dpercy</a>'s max variant would avoid rewarding such discussions.</p>
<p>Just like to point out that the assumption here is that upvotes is a metric of quality. On lobste.rs as in any other social context, upvotes/support is a measure of popularity.</p>
<p>There are many instances where the two are correlated, and some important instances where they are not.</p>
<p>Just like to point out that the assumption here is that upvotes is a metric of quality. On lobste.rs as in any other social context, upvotes/support is a measure of popularity.</p>
</blockquote>
<p>I fully agree: upvotes are a metric of popularity and not of quality. But this proposal does not assume that.</p>
<p>Regardless of what upvotes represent, currently threads are sorted in descending order of (upvotes, time). All that this proposal suggests, is to change that to (sum(upvotes of root-and-children), time).</p>
<p>The only assumption here is: if ranking a thread by upvotes of its root comment is considered good (and at the moment it is), then ranking by the sum of the upvotes of the whole thread is better.</p>
<p>Tangential to your effort here, on a personal note, if I find a discussion interesting I read all the posts regardless of ranking. On the discussions I find worthwhile I find that post quality is unrelated to upvotes.</p>
<p>On some discussions I find the top voted comment has devolved into a long tail of niche discussion, often acrimonious, which is not as useful as later posts.</p>
<p>Worst to pick out of the noise are replies to replies which are great but buried in not so great comments.</p>
<p>I feel kind of honored to be taken as an example, so please let me share my personal thoughts (TL;DR below):</p>
<p>It is impossible to find <em>the</em> definite ranking system, but let me elaborate by using 3 extremes to see where the current and proposed systems fail:</p>
<ol>
<li>
<em>highly controversial root comment</em> with score 0 (100 upvotes, 100 downvotes), logically controversial responses with median 0,</li>
<li>
<em>root comment preaching to the choir</em> with logically high score and usually high-score responses,</li>
<li>
<em>low-score root comment with high-score responses</em> (like the presented case)</li>
<li>
<em>OC high-value root comment</em> with high score and high-value responses</li>
</ol>
<p>It all depends on personal preference though, but I personally would like more controversial topics to be on top and less controversial ones to be secondary. The reason for that is that you otherwise might end up with echo-chambers, given users are encouraged to preach to the choir for a high karma score. As a tangent, I would prefer two karmas for each user: A "paragon-karma" and "renegade-karma" whose sum would be an "influence" score (in the end up- or downvotes both reflect a certain influence) and whose difference would be a "non-controversiality-score" (a low difference reflects controversiality, a high score the opposite). All in all, maybe we should move away complete from the miriad of downvote-options on lobsters and simply have an "agree" and "disagree". If something is spam, you can report it, if something is incorrect, you can write a response rectifying it (which would then be subject to a vote of agreement and disagreement, depending on how well you state your case).</p>
<p>The proposal to add replies into the weight would not change the ranking of controversial topics (1), but even moreso weigh comments preaching to the choir (2) because everyone replying would aim to also get some karma. It would solve the presented case (3) though. Case (4) would also be favoured.</p>
<p>The proposal by <ahref="https://lobste.rs/~dpercy"rel="ugc">@dpercy</a> to the take the max would not change (1)'s ranking, probably not affect (2) but help (3) as well. (4) is also positively affected.</p>
<p>TL;DR, here's my proposal so more highly-controversial topics (those that are interesting) are more favoured: Consider root comment and replies as equals and only take <em>influence</em> (sum of up- and downvotes) as a score.</p>
<p>This benefits (1), which in the current form end up at the bottom, which makes zero sense. It also benefits (2) and (4), which is a forced compromise, given the score does not really show how "original" a comment is (this is why Reddit probably introduced awards to allow users to weigh very good posts). By taking all replies into account, it also, of course, benefits (3), which makes sense to push up.</p>
<p>Replies with no votes increase the influence of the thread by 1, indeed, but isn't the purpose of the score to show what the hivemind thinks? By scoring them as zero (which was proposed here), you effectively value an elaborated response lower than a simple upvote of the original comment. Instead, why not simply "fold" long subthreads so they don't take up too much space?</p>
<p>As another tangent: By weighing root and children, this might encourage people to respond to "deep" threads instead of writing a new post.</p>
<p>Let's see what <ahref="https://lobste.rs/~pushcx"rel="ugc">@pushcx</a> decides in the end. :)</p>
<p>I've moved most of the <ahref="https://github.com/lobsters/lobsters/blob/cf52ab9f3f0b3ac805effc2e718f15922cb67332/app/models/comment.rb#L340"rel="ugc">vote</a> into the db, but <ahref="https://github.com/lobsters/lobsters/blob/cf52ab9f3f0b3ac805effc2e718f15922cb67332/app/models/comment.rb#L219"rel="ugc">not all</a>. If <code>calculated_confidence</code> finished its move into the db almost any algorithm would be faster than the current implementation.</p>
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.