Imported from PythonShe/Inkuna (
AGENTS.md). Install upstream withnpx skills add PythonShe/Inkuna. Copyright stays with the author.
Inkuna Monorepo Rules
Inkuna is a minimalist book reader (EPUB now; CBZ/CBR comics planned) for iOS
and Android. Apple Books-level feel is the quality bar. CJK support — vertical
writing, CJK-aware search — is a core product goal, not an afterthought.
License: AGPL-3.0. Website: inkuna.app.
| Directory | Component | Tech Stack | Description |
|---|---|---|---|
core/ |
Inkuna Core | Rust workspace + UniFFI | All non-UI logic: library DB, import, formats, metadata, progress, search, and the reader layout engine (parse → style → shape → paginate → display lists); later annotations/sync |
apps/ios/ |
Inkuna iOS | UIKit + XcodeGen | Native iOS shell (app.inkuna.ios), min iOS 18 |
apps/android/ |
Inkuna Android | Kotlin + Jetpack Compose | Native Android shell (app.inkuna.android), minSdk 33 |
scripts/ |
Build scripts | bash | Core cross-builds + UniFFI bindings generation |
website/ |
Inkuna Website | Astro (static output) + pnpm | Marketing site for inkuna.app, built with Astro and deployed via Cloudflare Pages |
Reader boundary: the Rust core owns layout — XHTML parsing, styling, text
shaping with bundled fonts, line breaking, pagination — and emits per-page
glyph-run display lists; the shells own drawing (Core Text /
Canvas.drawGlyphs) and all interaction.
Stack Policy
- Latest everything: newest stable Rust, Swift, Kotlin, Gradle, AGP, JDK, SDKs, and all dependencies. When bumping, query the registries (crates.io / Google Maven / Maven Central / services.gradle.org / npm) for actual latest stable — never guess from training data. A dependency that caps another below latest loses (e.g. refinery is deferred because it pins rusqlite below current).
- pnpm, never npm/yarn:
website/is the only JavaScript component and it uses pnpm exclusively —pnpm-lock.yamlis the committed lockfile and the pnpm version is pinned bypackageManagerinwebsite/package.json. Never runnpm/npx/yarnin this repo (pnpm dlxreplacesnpx). - Mainstream crates over hand-rolling: if a well-adopted crate exists for
a need, use it. Designated choices for upcoming needs:
deadpool-sqlite(concurrent DB access),notify(watch-folder import),rayon(parallel batch import),argon2(if auth lands),lofty(if audio lands). - Format strategy: reflowable formats (EPUB, MOBI, AZW3, TXT) normalize to EPUB in the core at import; fixed-layout (PDF, CBZ/CBR) get dedicated navigators. MOBI/AZW3 support is DRM-free files only — never implement or integrate DRM circumvention.
Scope Rules
- These root rules cover only monorepo-level constraints, not component implementation details.
- For a task within
core/,apps/ios/,apps/android/, orwebsite/, also follow that directory'sCLAUDE.md. - For tasks spanning components (e.g. an FFI change), follow this file first,
then each affected component's rules. After any
core/crates/inkuna-ffichange, regenerate bindings via BOTHscripts/build-core-*.shbefore building the shells. - Each
AGENTS.mdis a verbatim copy of its siblingCLAUDE.md(for Codex). After editing anyCLAUDE.md, re-copy it over the siblingAGENTS.mdin the same commit.
Documentation Priority
docs/dev/architecture.md— architecture decision record (why Rust core + native shells, version targets, roadmap). Read before structural changes.
CI/CD (GitHub Actions)
deploy-website.yml— buildswebsite/with Astro (pnpm viapnpm/action-setup,pnpm install --frozen-lockfile) and deploysdist/to Cloudflare Pages (projectinkuna, direct upload) on everymainpush touchingwebsite/.release-ios.yml/release-android.yml— tag-driven releases (ios-vX.Y.Z+N/android-vX.Y.Z+N): core tests gate the build; iOS archives with manual signing and uploads to TestFlight, Android builds a signed APK attached to a GitHub release.release-notes.yml— standalone/reusable AI release-notes generation (OpenAI chat completions; model/endpoint overridable via theLLM_MODEL/LLM_BASE_URLrepo variables), called by both release workflows.- Versions live in the component files (
core/Cargo.tomlworkspace version,apps/ios/project.yml,apps/android/app/build.gradle.kts); bump them ONLY viascripts/bump-version.sh <core|ios|android> <major|minor|patch|X.Y.Z> [--tag]— the release workflows fail on tag/file version mismatch. - Secrets and signing material never enter git history: Android signing comes
from
ANDROID_*secrets (keystore + credentials also live locally in~/Documents/Dev/inkuna/), iOS fromAPPSTORE_*/IOS_*/APPLE_TEAM_IDsecrets (ExportOptions.plist is generated at CI time), website deploys fromCLOUDFLARE_*secrets, notes fromOPENAI_API_KEY.
Git Conventions
- Commit and push in logical groups as work completes; the owner has granted this standing authorization.
- Never add "Generated with Claude Code" /
Co-Authored-By: Claudefooters. - Commit message format:
<type>(<scope>): <description>with typefeat/fix/refactor/docs/chore/test/perf/styleand scope:
| scope | Applicable scenario |
|---|---|
core |
Changes within core/ |
ios |
Changes within apps/ios/ |
android |
Changes within apps/android/ |
website |
Changes within website/ |
docs |
Changes under docs/ |
workspace |
Repo-root-level changes (CLAUDE.md, scripts/, etc.) |
Cross-component changes may combine scopes (core,ios); sub-dimensions may
refine them (core/epub, ios/reader).
Directory Boundaries
- The repository root is not the root of any single component; do not place component-specific source or config at the root.
- Generated artifacts are never committed:
apps/ios/Generated/,apps/ios/Frameworks/,apps/ios/Inkuna.xcodeproj/,apps/android/app/src/generated/,apps/android/app/src/main/jniLibs/,core/target/. - No personal information (names, emails) in README or other public-facing docs.
Machine/Toolchain Gotchas (this dev machine)
- Two Rust installs exist: Homebrew (
/opt/homebrew/bin, no cross targets) shadows rustup. The scripts pinRUSTC+rustup run stable; do the same for any new cargo invocation that cross-compiles. apps/android/gradle.propertiespinsorg.gradle.java.hometo the latest Homebrew OpenJDK; if a Gradle release ever lags the JDK, fall back to Android Studio's JBR.rustup run stabledoes NOT shadow PATH lookups here: pinRUSTC(andRUSTDOCfor doctests) torustup which --toolchain stable ...or builds silently use the Homebrew rustc, which fails cross-compiles with E0463.- A global cargo config redirects target-dir to
~/.sonelis/cargo-target; the scripts override withCARGO_TARGET_DIR=core/target.