These rules apply to every change, whether a person or an AI agent makes it. They keep Weft small, fast and consistent. If a rule gets in your way, open an issue before breaking it.
gradle/libs.versions.toml.material-icons-extended. Icons are Solar (Linear) vector drawables in app/src/main/res/drawable, made by tools/solar_icon.py (see CLAUDE.md); never mix in another set.AppContainer), no chart library (use Compose Canvas), no image library, no networking library.foss flavor must never contain Google or Play Services code. Put Play-only code in src/play (stand-ins in src/noPlay) and Google Drive code in src/drive (play and github; stand-ins in src/noDrive).isMinifyEnabled = true, isShrinkResources = true. Check the APK size after changes that could grow it (target: under 5 MB; today it is about 2.8 MB).The app was called Sprout until 1.5.0 (2026-11-01). User-facing text, class names (WeftApp, WeftType…), export file names and links say Weft. These stored names stay sprout on purpose, so Sprout installs update to Weft and keep their data; never rename them:
app.sprout.habits and the app/sprout/habits source folders. A new package would be a new app on Google Play, GitHub/Obtainium and F-Droid, leaving users behind on Sprout.sprout.db, the backup file’s "app":"sprout" tag (old and new versions read each other’s files), the Drive backup names sprout-backup-*, the sprout:// links (still accepted next to weft://) and the app.sprout.habits.action.* intent actions.SPROUT_KEYSTORE_* variables and GitHub secrets, keystore/sprout-release.jks and its alias: renaming them only risks a broken release build.-github.apk and by SHA256SUMS.txt, so release assets keep that ending and that file.strings.xml and the user’s own notes, worked out on the phone (domain/NoteSuggestions.kt). No model, no network. More → Journal turns them off or clears what they learned.design/TOKENS.md.design/ (one PNG per screen, e.g. today.png, journal.png, icon-picker.png). Exact colors and icon paths are in design/TOKENS.md. Since 2026-10-19 they are captures of the app with the sample data (27 Sep); when a screen changes, recapture its PNG too. All captures use the default Amber accent; only the store images in release/ (and fastlane) use Blue, the logo’s colour. habit-card-states, notifications, update-dialog, support-prompt and drive-restore-sheet are still mockups.release/phone-screenshots/ are framed by tools/store_shots.py, which keeps the status bar clear of the frame’s corners.docs/SPACING.md: 16 dp side padding, cards 8 dp apart, sections 16 dp apart, 16 dp inside a card, section labels with 16 dp above and 8 dp below, a 44 dp habit tile wherever a habit is listed.Canvas and use only the app colors: primary for done, the light primary tone for partial, outline grey for the rest. Every chart mark that can be tapped shows its numbers and has a content description.docs/screenshots/ (see release/README.md for how they are staged) in the same commit. Don’t invent a new value; copy the same element on Today.docs/TYPOGRAPHY.md, available as WeftType.* from ui/theme/Type.kt. Never set a font size or weight anywhere else, and never read MaterialTheme.typography in a screen. Every style, headings included, uses the chosen font (Lexend by default).ui/components/Haptics.kt, so the same kind of action always feels the same. A new switch, chip, choice or stepper gets the matching one.CappedFontScale.DateRangeSheet, DatePickerSheet, TimePickerSheet and HabitFilterSheet in ui/components. TimePickerSheet (the wheel) is the only time picker: every time setting uses it. Dialogs are only for confirming something (delete, import). Every sheet is a WeftSheet (ui/components): it opens fully and stops at 90% of the screen, so put long content in a scrolling list with Modifier.weight(1f, fill = false) and keep the main button below it. Full screens are only for creating or editing (New habit, New note).TabHeader, plus the filter chips on Journal and Insights) sit above the list, not inside it, so they stay in place while the list scrolls.HabitTile and a partial ring is ProgressRing (ui/components/HabitTile.kt, sizes HabitTileSize 44dp / HabitTileCorner 14dp / HabitTileIconSize 22dp, ProgressRingSize 44dp / ProgressRingStroke 4dp). Never hand-build either; grouped lists use ui/components/Grouping.kt.app/src/androidTest and a smoke check; new logic gets a unit test.domain/ with unit tests. Scoring and streak rules are easy to get subtly wrong, so change the tests first..claude/skills/. It explains how the feature works, where the code is and how to check it.python3 tools/verify/smoke.py, see CONTRIBUTING.md).tools/verify/smoke.py for new behaviour, update or add the feature’s skill, and add anything left undone to TODO.md.