agentleFS
garfiecgarfiec/Librechat-Mobile/CLAUDE.md

Librechat-Mobile

CLAUDE.md149 starsChanged 7 days agoApache-2.0View on GitHub

Native mobile client for LibreChat (Android & iOS). Connects to existing LibreChat backend servers (no backend changes). Users specify server URL during onboarding. Each module has its own CLAUDE.md with specific guidance. - Build for what users hit. Handle an edge case when a user on a supported server reaches it in normal use, or when it loses data, leaks a credential or strands the session. A rare glitch that a retry, refresh or reopen clears doesn't earn new state,…

The file

We split this CLAUDE.md into the rules it gives an agent

6 rules. About 0 are common, 1 is distinctive, and 5 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    A module CLAUDE.md holds rules, not history: Every agent touching the module loads it, so add only a rule that governs many files, once, …

    line 45
  2. Very specific:

    Never branch on BuildConfig.FLAVOR or DistributionChannel at a call site (detekt bans the import in non-test code.

    line 65
  3. Very specific:

    Create the module directory under feature/

    line 73
  4. Very specific:

    Create di/<Feature>Module.kt with a Koin module { } containing viewModelOf(::YourViewModel) definitions

    line 77
  5. Very specific:

    Add the module to sharedKoinModules (shared/src/commonMain/.../di/SharedKoinModules.kt) — the single list both Android …

    line 78
  6. Very specific:

    Use koinViewModel() in screen composables to inject ViewModels

    line 79

≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

Switchboard

Native mobile client for LibreChat (Android & iOS). Connects to existing LibreChat backend servers (no backend changes). Users specify server URL during onboarding.

Tech Stack

  • UI: Jetpack Compose + Navigation Compose 3 (Nav 3)
  • DI: Koin (KMP-ready)
  • Network: Ktor Client (OkHttp engine)
  • Serialization: Kotlinx Serialization
  • Local Storage: Room (cache), DataStore (prefs), EncryptedSharedPreferences (tokens)
  • Build: Gradle 9.8.2, AGP 9.5.1, Kotlin 2.4.10, compileSdk 37, minSdk 26

Module Layout

app/                  → Single Activity, adaptive navigation (phone/tablet)
shared/               → KMP shared framework (iOS entry point, shared navigation)
build-logic/          → 13 convention plugins for consistent Gradle config
core/common/          → Result type, dispatcher DI, coroutine scopes, extensions
core/model/           → @Serializable data classes (pure Kotlin, no Android deps)
core/network/         → Ktor client, 16 API services, SSE client, auth interceptor
core/data/            → Room DB, DataStore, EncryptedSharedPrefs, repository impls
core/ui/              → Material 3 theme, shared composables
feature/auth/         → Server URL, Login, Register, 2FA, OAuth, Forgot Password
feature/chat/         → Real-time chat with SSE streaming, message rendering
feature/conversations/→ Paginated list, CRUD, tags, search, export/import
feature/settings/     → Account, appearance, about, danger zone
feature/agents/       → Agent marketplace with search and categories
feature/files/        → File upload, management, image viewer
testing/speech-fixture/ → Stand-in speech provider APK for scripts/verify-speech-services.py (never ships)

Each module has its own CLAUDE.md with specific guidance.

Writing code here

  • Build for what users hit. Handle an edge case when a user on a supported server reaches it in normal use, or when it loses data, leaks a credential or strands the session. A rare glitch that a retry, refresh or reopen clears doesn't earn new state, a Job, a flag or a new test file. Fix the reported scenario with the smallest change, not its hypothetical neighbours; reuse or delete code before adding it. This applies to review findings too, including /code-review --fix.
  • Comments state present-tense rules a plausible edit would break, in a line or two. No history, no story of how a bug was found or fixed, no issue numbers as narrative: that goes in the commit and PR.
  • A module CLAUDE.md holds rules, not history. Every agent touching the module loads it, so add only a rule that governs many files, once, with its condition in the same sentence. A rule that guards one line belongs at that line, or in a test or detekt rule.
  • Tests go in the class's existing test file; a new test file is for a new class.

Architecture Rules

  • Feature modules depend on :core:* only, never on each other
  • Single Activity with Nav 3 (NavDisplay + NavBackStack<NavKey> + entryProvider)
  • Unidirectional data flow: UI → ViewModel → Repository → API/Room
  • Room is a read-through cache; server is source of truth
  • Custom SSE parser over raw ByteReadChannel (not Ktor SSE plugin)
  • UI is style-agnostic: the user's Material / Liquid Glass choice flows down as a CompositionLocal from LibreChatTheme, and Adaptive* components in :core:ui self-theme into one of two variants. Screens use Adaptive* (detekt bans raw M3 chrome) and don't branch on style. See "The pattern" in core/ui/CLAUDE.md.
  • Distribution channels: open source (foss flavor, com.garfiec.librechat; Xcode Release) vs store (play flavor, com.garfiec.librechat.play; Xcode Release-AppStore). A behaviour that differs between them is a field on ChannelPolicy (:core:common), injected where needed; a preference default that differs is a perChannel(…) default in AppPreferences (:core:data). Never branch on BuildConfig.FLAVOR or DistributionChannel at a call site (detekt bans the import in non-test code; :core:common uses it from its own package), and don't call ChannelPolicy.pick from a feature either: add a field. A channel that can't be determined fails at launch by design. See docs/RELEASING.md "Distribution channels".

Adding a New Feature Module

  1. Create the module directory under feature/
  2. Apply the convention plugin in build.gradle.kts — this auto-applies Koin + Compose deps:
    • librechat.kmp.feature — for KMP modules with shared iOS + Android code (most features)
    • librechat.mobile.feature — for Android-only modules
  3. Create di/<Feature>Module.kt with a Koin module { } containing viewModelOf(::YourViewModel) definitions
  4. Add the module to sharedKoinModules (shared/src/commonMain/.../di/SharedKoinModules.kt) — the single list both Android (LibreChatApplication) and iOS (IosSharedModule) start from, so a module registered once is wired on both platforms
  5. Use koinViewModel() in screen composables to inject ViewModels

Backend Quirks

  • Mutation endpoints wrap body in arg field: { "arg": { ... } }
  • Two-phase SSE: POST → { streamId }, then GET stream. streamId === conversationId
  • GET /api/config drives feature availability — never hardcode
  • ua-parser-js middleware rejects non-browser User-Agents with 403 (workaround: Chrome UA string)
  • Refresh token sent via request body (not HTTP-only cookies)
  • iOS SSE streaming uses a custom NWConnection-based HTTP/1.1 transport (core/network/src/iosMain/.../sse/SseHttpTransport.ios.kt) to bypass NSURLSession's undocumented text/* content-type buffering. See core/network/CLAUDE.md SSE section for the two-layer buffering story.

Upstream Sync

  • upstream/ — Git submodule of the official LibreChat repo. Read-only reference for API and web app parity. Do not modify.
  • UPSTREAM_VERSION — Tracks which official tag/commit this mobile build is based on. Updated by the /sync-upstream skill.
  • backendTargetVersion (root version.properties) — single source of truth for the targeted backend; must match the tag in UPSTREAM_VERSION (without v prefix). A core/common Gradle task code-generates BackendVersion.SUPPORTED_BACKEND_VERSION from it, and release.yml reads it for release notes. Edit the property, not the constant.
  • /sync-upstream — Claude Code skill to diff upstream releases, identify gaps, propose changes, and implement them with user approval. Uses Agent Teams (investigator, android-expert, implementer, verifier).
  • scripts/web-assets.json — registry of the third-party JavaScript the artifact/diagram/math WebViews execute (KaTeX, mermaid, marked, highlight.js, Tailwind, Babel, React, and the npm modules React artifacts may import, bundled from scripts/artifact-modules/). All of it is vendored into the app; none of it is fetched at render time. This is required for F-Droid, which rejects apps that download executable code without explicit opt-in consent — a <script src="https://cdn…"> in a WebView is exactly that. It also closes a silent-drift hole: an unversioned CDN URL served whatever the CDN resolved that day, and two libraries had already broken that way without failing a build or a test. scripts/vendor-web-assets.py downloads the pins and CI verifies the tree against a sha256 lock. Repin via /update-web-assets, never by editing a vendored file. Adding a new WebView dependency means adding a registry entry in the same PR. See feature/chat/CLAUDE.md for how a page resolves them per platform.
  • scripts/mirrors.json — registry of upstream constants the client copies by hand, because the server never serves them (which providers take documents natively, which MIME types the parser extracts, which feedback reasons the write route accepts). These drift silently: nothing fails to decode and nothing errors, so a sync's ordinary diff sweep reads them as inert constant edits. scripts/check-mirrors.py diffs each watched region between two upstream revisions and names the Kotlin file to reconcile; /sync-upstream runs it at Phase 0. Adding a hardcoded mirror means adding a registry entry in the same PR — a mirror nobody registered is one nobody will notice going stale.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.