Contributing
Module boundaries, code style, and commit conventions to match, plus a few good places to start.
WM Keyboard doesn’t have a formal contribution process, issue templates, or a CI pipeline yet: there’s no bot to satisfy, only a codebase to fit into cleanly. This page covers how the repository is organized and styled, and where a first change is easiest to land, so a pull request needs as little back-and-forth as possible.
Finding your way around the modules
Section titled “Finding your way around the modules”The repository is a single Gradle build of 19 library modules plus :app: 16
under :core:* (config, common, language, input, prediction,
emoji, theme, icons, tools, content, addons, voice, settings,
feedback, plugins, intelligence) and 3 under :feature:* (tools,
addons, ime). A class’s module is visible from its file path, not its
package: everything still lives under com.wasimaster.wmkeyboard.*.
Dependencies only point downward, and it’s a real Gradle project graph, not just a convention:
:core:configsits at the very bottom: it carries build flags and API keys that every other library reads.:core:commonbuilds on:core:configfor shared utilities every module needs.- The rest of
:core:*is one engine or store per feature area, each depending on only the lower modules it actually needs::core:settings, for instance, depends on eleven other:core:*modules because the settings screens surface controls for all of them, while:core:feedbackneeds just:core:settingsand:core:common. :feature:*sits above:core:settings, since the feature modules read the settings repository.:feature:ime(the keyboard runtime itself) depends on all 16:core:*modules plus:feature:tools.:appsits on top of everything: the settings activity, manifest, and assets, wired to all 19 library modules.
No module declares a dependency on anything above it in this list: there’s
no dedicated boundary-checking task, the constraint is just the Gradle project
graph itself. :app/build.gradle.kts and each module’s own build.gradle.kts
are the ground truth if a dependency direction ever looks surprising.
One module breaks the pattern deliberately: :tools:dictc, the host-side
(pure JVM, no Android) dictionary compiler, doesn’t depend on :core:prediction
as a project: its build.gradle.kts pulls five specific files
(Main.kt, Trie.kt, PackedTrie.kt, PackedTrieCodec.kt, DictionaryLoader.kt)
straight out of that module’s source directory instead, so the compiler and
the reader share one literal implementation and can never drift apart.
Matching the code style
Section titled “Matching the code style”There’s no autoformatter run over the codebase: .editorconfig at the repo
root notes that a mass reformat of a codebase this size would bury every real
finding in diff noise, so it documents the style the code already follows
rather than enforcing one. The values that matter most: 4-space indent,
140-character line length, Android Studio’s ktlint code style, and unlimited
star-import thresholds (star imports are allowed). New code should just look
like the code around it.
Static analysis runs in two layers, both configured under config/:
- detekt (
config/detekt/detekt.yml), layered on detekt’s own defaults.maxIssuesis0andexcludeCorrectableisfalse, but the rule weights are deliberately uneven: complexity, comment, and deprecation rules are turned down to zero weight, while correctness rules (the ones that catch actual bugs) stay at full weight. The input-dispatch and layout-resolution code is genuinely branchy, so style metrics like cyclomatic complexity aren’t a useful signal there. - Android Lint (
config/lint/lint.xml). The app module turns oncheckAllWarnings(roughly 200 checks that are off by default) andabortOnError, and escalates specific checks (NewApi,WrongThread,Recycle,StaticFieldLeak,HandlerLeak,MissingPermission, and others) toerror, each with an inline comment explaining why.
One task runs everything:
./gradlew staticAnalysisThis runs Android Lint against the full flavor of the app module, detekt (with
full type resolution) against both the full and lite flavors of the app
module plus its unit tests, and per-module detekt across 18 of the 19 library
modules (:core:config has no Kotlin sources to check, and :tools:dictc is
skipped deliberately; see below). Nothing currently runs this automatically
on a pull request; treat it as the check to run yourself before asking for
review, not as a gate that will catch a miss for you.
Picking a first contribution
Section titled “Picking a first contribution”A few things worth knowing before you start on any of these:
- Dictionaries are mostly not in this repo.
app/dictionaries-src/bundles exactly two seed lists (en.txtandbn.txt, each a plainword countline per entry) compiled into.wmdictassets at build time. The much larger catalog of downloadable word lists is fetched at runtime from a separate repository, wasi-master/wmkeyboard-data, perDictionaryCatalog.kt. Contributing a new or improved word list for an existing language almost always means a pull request to that companion repository, not this one: the dictionary pipeline page covers the format. - Emoji keyword packs follow the same split. The 125 downloadable packs
(per
EmojiDictCatalog.kt) also come fromwmkeyboard-data; what lives in this repo is the generation tooling undertools/emoji/(generate_gemoji.py,export_keyword_pack.py,generate_catalog.py,add_names.py,generate_dict_catalog.py). - Built-in themes are Kotlin, not data files. The eight built-in themes
are
ThemeSpec(...)object literals incore/theme/.../PaletteThemes.kt: contributing one means a Kotlin change to:core:theme. The.wmtheme.jsonformat you may have seen is the export format a user gets from sharing a theme they built in the app, and doesn’t require touching this codebase at all. See sharing themes and, for distributing one more widely, addon repositories. - Docs are this site. Everything under
docs/src/content/docs/is an Astro/Starlight site with its own house rules (page anatomy, component usage, and the “verify every claim in code” ground rule) written down in the docs project’s ownCONTENT_GUIDE.md. Read that first if you’re adding or editing a page.
Opening a pull request
Section titled “Opening a pull request”Commits in this repo follow Conventional Commits:
type(scope): summary, lowercase, present or imperative tense, for example
fix(addons): validate repo URL scheme or docs(tools): rewrite the search page. The common types are feat, fix, docs, test, refactor,
chore, style, and build; scopes generally track a module or doc section
name (addons, plugins, tools, icons, emoji, typing, lint,
about, and so on). Repo-wide changes drop the scope entirely (docs: ...,
feat: ...).
Before opening a pull request, run the checks a reviewer would otherwise have to run for you:
./gradlew staticAnalysis./gradlew testFullDebugUnitTestSee Testing for what that second task covers and where its tests live.
The app’s own About screen links back to its source at github.com/wasi-master/WMKeyboard: that’s the repository issues and pull requests belong on. Changes to word lists or emoji keyword packs belong on wasi-master/wmkeyboard-data instead, per the “Picking a first contribution” section above.
Details & edge cases
Section titled “Details & edge cases”- Lite-flavor detekt only runs where lite actually differs.
staticAnalysisruns a separate lite-flavor detekt pass for just:core:voice,:core:intelligence, and:feature:ime: the only modules whosesrc/litesource set replaces real implementations (ML Kit, LiteRT) with stubs. Every other module’s full and lite sources are identical, so a second pass over them would just repeat the first. :tools:dictcis excluded fromstaticAnalysison purpose. Since its whole source set is symlinked from:core:prediction, analyzing it would report the same five files a second time under a different module name.- A stricter local pass exists, but it’s Kotlin-only. Every module’s
build.gradle.ktsgatesallWarningsAsErrorson awarningsAsErrorsGradle project property (./gradlew build -PwarningsAsErrors=true), off by default. Android Lint’s ownwarningsAsErrorsflag is hardcoded tofalseinapp/build.gradle.ktsand doesn’t read that property: the two are separate switches, even though they share a name. - Third-party licenses and attribution for bundled dependencies, data-pack sources, and online services the app calls are listed on the in-app About & licenses screen. Check there before adding a new third-party dependency or data source, since it needs an entry too.
- The docs site’s own GitHub links are still placeholders.
astro.config.mjs’ssitevalue and its GitHub-icon/edit-link URLs use a different casing of the repo path than the one the app’s About screen links to; both resolve to the same place today, but the docs project’s own handoff notes flag this as unresolved, so don’t treat either URL inastro.config.mjsas final.
