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,…
In this repository13 other files it gives its agents.
- app/CLAUDE.mdCLAUDE.md
- build-logic/CLAUDE.mdCLAUDE.md
- fastlane/CLAUDE.mdCLAUDE.md
- .github/CLAUDE.mdCLAUDE.md
- shared/CLAUDE.mdCLAUDE.md
- audit-depsSkill
- audit-i18nSkill
- audit-paritySkill
- marketing-refreshSkill
- release-highlightsSkill
- small-screensSkill
- sync-upstreamSkill
- update-web-assetsSkill
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
- Distinctive: line 45
A module
CLAUDE.mdholds rules, not history: Every agent touching the module loads it, so add only a rule that governs many files, once, … - Very specific: line 65
Never branch on BuildConfig.FLAVOR or DistributionChannel at a call site (detekt bans the import in non-test code.
- Very specific: line 73
Create the module directory under feature/
- Very specific: line 77
Create di/<Feature>Module.kt with a Koin module { } containing viewModelOf(::YourViewModel) definitions
- Very specific: line 78
Add the module to sharedKoinModules (shared/src/commonMain/.../di/SharedKoinModules.kt) — the single list both Android …
- Very specific: line 79
Use koinViewModel() in screen composables to inject ViewModels
≈ “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.
Files
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.mdholds 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, andAdaptive*components in:core:uiself-theme into one of two variants. Screens useAdaptive*(detekt bans raw M3 chrome) and don't branch on style. See "The pattern" incore/ui/CLAUDE.md. - Distribution channels: open source (
fossflavor,com.garfiec.librechat; XcodeRelease) vs store (playflavor,com.garfiec.librechat.play; XcodeRelease-AppStore). A behaviour that differs between them is a field onChannelPolicy(:core:common), injected where needed; a preference default that differs is aperChannel(…)default inAppPreferences(:core:data). Never branch onBuildConfig.FLAVORorDistributionChannelat a call site (detekt bans the import in non-test code;:core:commonuses it from its own package), and don't callChannelPolicy.pickfrom a feature either: add a field. A channel that can't be determined fails at launch by design. Seedocs/RELEASING.md"Distribution channels".
Adding a New Feature Module
- Create the module directory under
feature/ - 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
- Create
di/<Feature>Module.ktwith a Koinmodule { }containingviewModelOf(::YourViewModel)definitions - 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 - Use
koinViewModel()in screen composables to inject ViewModels
Backend Quirks
- Mutation endpoints wrap body in
argfield:{ "arg": { ... } } - Two-phase SSE: POST →
{ streamId }, then GET stream.streamId === conversationId GET /api/configdrives 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 undocumentedtext/*content-type buffering. Seecore/network/CLAUDE.mdSSE 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-upstreamskill.backendTargetVersion(rootversion.properties) — single source of truth for the targeted backend; must match the tag inUPSTREAM_VERSION(withoutvprefix). A core/common Gradle task code-generatesBackendVersion.SUPPORTED_BACKEND_VERSIONfrom it, andrelease.ymlreads 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 fromscripts/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.pydownloads 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. Seefeature/chat/CLAUDE.mdfor 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.pydiffs each watched region between two upstream revisions and names the Kotlin file to reconcile;/sync-upstreamruns 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.
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
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.

