KMReader
AGENTS.md118 starsChanged 7 days agoMITView on GitHub
Rules that must not be violated when working in this repository. For architecture and feature details, read the code. Subsystem conventions and invariants live in the repo-conventions skill (.agents/skills/repo-conventions/SKILL.md) — load it when working on the reader, sync/offline/persistence, dashboard, detail pages, or platform UI placement. KMReader is a native SwiftUI client for Komga. iOS 17+ / macOS 14+ / tvOS 17+, Swift 6, Xcode 15+. There are no XCTest targets: validate with builds and manual testing. All builds run through…
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,439 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository8 other files it gives its agents.
- appstore-releaseSkill
- changelogSkill
- docsSkill
- localizationSkill
- pr-workflowSkill
- repo-conventionsSkill
- simulatorSkill
- testflightSkill
The file
We split this AGENTS.md into the rules it gives an agent
11 rules. About 0 are common, 2 are distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 11
All builds run through the Makefile (wrapping misc/xcode.py).
- Very specific: line 26
After changing code: make format, then make build.
- Very specific: line 31
Git-facing text: commit messages, PR titles/bodies, and review comments are always in English.
- Distinctive: line 38
No stored variables in view bodies.
- Very specific: line 43
Do not store async/throwing/@Sendable closures in SwiftUI View value types (iOS 17 AttributeGraph crash risk).
- Very specific: line 45
No patch-style fixes for structural problems — no compensating flags/delays/counters around a broken ownership boundary.
- Very specific: line 47
When a change alters a lifetime, ownership, persistence, navigation, platform, reader-mode, or UI-placement boundary, update the …
- Distinctive: line 48
No hand-rolled fallback shims for newer OS APIs.
- Very specific: line 50
Never render an empty HStack/VStack.
- Very specific: line 61
All logging goes through AppLogger (OSLog subsystems/categories).
- Very specific: line 65
When building JSON strings for storage or cache keys, use JSONSerialization with sortedKeys for stable raw values.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.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
AGENTS.md
Rules that must not be violated when working in this repository. For architecture and feature details, read the code. Subsystem conventions and invariants live in the repo-conventions skill (.agents/skills/repo-conventions/SKILL.md) — load it when working on the reader, sync/offline/persistence, dashboard, detail pages, or platform UI placement.
Project
KMReader is a native SwiftUI client for Komga. iOS 17+ / macOS 14+ / tvOS 17+, Swift 6, Xcode 15+. There are no XCTest targets: validate with builds and manual testing.
Commands
All builds run through the Makefile (wrapping misc/xcode.py); never invoke xcodebuild directly.
make build # build all platforms (preferred validation)
make build-ios # platform-specific; run sequentially, never in parallel
make build-macos # (xcodebuild shares one DerivedData database)
make build-tvos
make run-ios-sim / make run-macos / make run-tvos-sim # run (device choice persisted in devices.json)
make format # format code after editing
make localize # update localizations; ./misc/translate.py list|update for keys
make bump / make minor / make major # version managementNever edit MARKETING_VERSION or CURRENT_PROJECT_VERSION in project.pbxproj by hand. Run make bump only when the user explicitly asks; the bump commit then rides along in the feature/fix PR, not a separate PR. When several PRs queue up at once, either fold a new change into an existing PR or move the bump commit to the last PR in the queue — the bump always lands on whichever PR merges last.
After changing code: make format, then make build. Simulator interaction goes through baguette (see the simulator skill): filter logs with subsystem com.everpcpc.kmreader (categories API, SSE, ReaderViewModel).
Coding Conventions
- Comments: minimal, English only; no issue/PR numbers (link issues in the PR description instead).
- Git-facing text: commit messages, PR titles/bodies, and review comments are always in English. Never add
Co-Authored-Bytrailers to commits. - UI frameworks: SwiftUI, UIKit, and AppKit are all acceptable; pick per feature and platform.
- No inline
Binding. - No
confirmationDialog. - One type per file.
- State:
@Observable, neverObservableObject. - Preferences:
@AppStoragein views,AppConfigelsewhere;UserDefaultsonly insideAppConfig.swift. - No stored variables in view bodies; avoid computed-property clutter there too.
- Platform differences via
PlatformHelperand#if os(...). - UIKit/AppKit interop in either direction is fine; be explicit about dependency injection and verify environment/data propagation across hosting boundaries.
- Banned: non-optional object-style environment dependencies (
@Environment(SomeType.self),@EnvironmentObject). Pass objects via initializers, context structs, or action closures; use non-object customEnvironmentKeys when needed. - Banned:
@unchecked Sendable,nonisolated(unsafe),unsafeBitCast, otherunsafe*escape hatches. Redesign instead. - Do not store async/throwing/
@Sendableclosures in SwiftUIViewvalue types (iOS 17 AttributeGraph crash risk); use concrete command types or passed-in services. - Animation boundaries: local implicit
.animation(..., value:)only for micro-interactions (press/hover/selected states); explicitwithAnimation {}for navigation, presentation, content, and pagination changes. No broad/root.animationon containers rendering lists.@AppStorage-backed lists are the exception: UserDefaults writes carry no transaction, sowithAnimationcannot animate them — drive row/section changes there with.animation(_:value:)on the list container, scoped to the changing value. Never wrap paginated List row insertions inwithAnimation— inserted rows animate from compressed to natural height, which reads as rows jumping taller after the page settles. - No patch-style fixes for structural problems — no compensating flags/delays/counters around a broken ownership boundary. Refactor toward the stable architecture even when it means rewriting a subsystem; end-state quality beats diff size.
- Temporary compatibility layers must say why they exist and what replaces them; treat them as debt.
- When a change alters a lifetime, ownership, persistence, navigation, platform, reader-mode, or UI-placement boundary, update the conventions in the same change:
AGENTS.mdfor repo-wide rules, therepo-conventionsskill for subsystem boundaries. - No hand-rolled fallback shims for newer OS APIs; gate features to the OS version that supports them natively.
- No force casts (
as!), especially on GRDBRowsubscripts; use the generic converting subscript (let date: Date = row["created_date"]) oras?with a fallback. - Never render an empty
HStack/VStack; put the condition around the stack itself so nothing renders when there is no content. - Lazy containers (
LazyVStack/LazyHStack/LazyVGrid) only for genuinely unbounded content (paginated or otherwise huge lists); eager stacks everywhere else — lazy stacks cache child frames and misplace children during animated layout updates. - Plain-style buttons and links (
.buttonStyle(.plain), text-or-label-only) must declare.contentShape(Rectangle())(or an equivalent hit shape) on the content inside the button's label so the whole frame is tappable; without it only the glyphs respond..contentShapeapplied outside on the button itself has no effect on hit-testing, regardless of button style. - Hardcoded
.framesizes are a last resort: never pin pixel dimensions to paper over a sizing difference (e.g. normalizing buttons whose glyphs have different bounding boxes). Fix the cause instead — pick glyphs from one visual family, use control sizes, or change the layout. - Banned:
fatalError/preconditionFailurein app logic — a reachable one crashes the app. Model impossible cases away instead (narrower enums, exhaustive switches). Exception:required init?(coder:)stubs. - Button styles: style buttons with
.adaptiveButtonStyle(_:), never.buttonStyle(...)directly. The adaptive style picks the glass styles on OS 26 and pairs the prominent tint with a readable foreground; a raw.borderedProminentrenders white on white with the white accent in dark mode. Use a raw style only where no adaptive case fits (e.g..borderlessfor an action inside aFormrow, which must not take the whole row's tap), and say why in a comment.
Additional patterns:
- Pass shared object dependencies explicitly at split/tab roots,
NavigationStackroots, sheets, full-screen covers, scene boundaries, and anyUIHostingController/NSHostingControllerboundary; do not assume environment inheritance survives snapshot/rotation/scene transitions. - New API endpoints belong in the appropriate service; keep request-building out of views.
- All logging goes through
AppLogger(OSLog subsystems/categories); user-visible errors throughErrorManager.shared(notifyfor transient success). - The Xcode project uses folder references (not groups); adding/removing files does not require editing
project.pbxproj. - Translate all supported languages after changing UI strings (see the
localizationskill). - Localization keys must be static literals: the strings extractor cannot see a key computed at runtime (e.g. a ternary inside
String(localized:)), so the catalog entry gets flaggedextractionState: staleon everymake localizeand risks deletion as unused. Branch outside the call, with oneString(localized:)per literal. - When building JSON strings for storage or cache keys, use
JSONSerializationwithsortedKeysfor stable raw values. - Types persisted through
RawCodableStoreand also conforming toRawRepresentablemust provide their own keyedinit(from:)andencode(to:); never encodeselffromrawValue, because that re-enters encoding and overflows the stack. Scalar catalog DTOs such asModelEngineandModelPurposestill use single-value containers. - Colors that vary only between light and dark mode belong in
Assets.xcassetsas color sets with light/dark appearances, referenced asColor.<name>— notcolorSchemebranching in views. Assets also carry alpha and can encode gradient-stop pairs (start/end as two assets). ReservecolorSchemereads for layout or logic differences; clusters of one-off decorative tints serving a single view may stay local. - SF Symbol fill/outline is a rendering concern, not data: models and enums expose the base (outline) symbol name, and the site that knows its rendering context applies
.symbolVariant(.fill)— never thread hardcoded*.fillnames or parallel filled-name parameters through view APIs. Exception: an icon filled in every context is part of the status's identity (e.g.checkmark.icloud.fill,exclamationmark.circle.fillin download statuses); models may return the.fillname directly. - Icon names with a shared meaning go through constants, not string literals:
AppIcon(actions, statuses, errors) andContentIcon(content types) inShared/Helpers, andReadStatus.icon/SeriesStatus.iconon the models. Add a constant when a glyph is used across features; one-off decorative glyphs (section identities, form rows) stay inline. - Standard animation curves are
Animation.appCurve(_:)(quick UI transitions) andAnimation.appSpring(overdamped, gesture-interruptible motion) — prefer them over ad-hoc.easeInOut/.snappyin new transitions. Haptics go throughHapticFeedback(Shared/Helpers) and fire only on discrete boundary crossings, never on continuous motion. - High-frequency progress-style updates (transfer progress, meters, byte counters) must be throttled at the source before reaching UI state: cap the rate (e.g. 10 Hz) and always emit the first and final values. Never forward raw callback frequency into view state. Animation is exempt.
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.

