- Kotlin 97%
- HTML 3%
Demo reports Ghost 6.53, and the demo blog's staff clients (comments, staff identity, analytics) answer on device, so a saved staff token no longer loops "could not check availability" against an unresolvable demo host. Real accounts unaffected (exact demo URL). DemoStaffApisTest: 5 of 9 red on the old code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KaiybZerte47xGhnSr3UAp |
||
|---|---|---|
| .github | ||
| .superpowers | ||
| app | ||
| baselineprofile | ||
| DESIGN | ||
| docs | ||
| gradle/wrapper | ||
| ios | ||
| marketing | ||
| pics | ||
| site | ||
| theme | ||
| tools | ||
| .gitignore | ||
| .gitleaksignore | ||
| AGENTS.md | ||
| AUDIT_REPORT.md | ||
| BACKLOG.md | ||
| BLOG_MIGRATION.md | ||
| build.gradle.kts | ||
| CHANGELOG.md | ||
| CHANGELOG_AGENT.md | ||
| CLAUDE.md | ||
| CONTRIBUTING.md | ||
| gradle.properties | ||
| gradlew | ||
| IMPROVEMENTS_REPORT.md | ||
| ISA.md | ||
| LICENSE | ||
| MONETIZATION.md | ||
| PLAY_CONSOLE_CHECKLIST.md | ||
| PRIVACY.md | ||
| README.md | ||
| settings.gradle.kts | ||
| STORE_LISTING.md | ||
| TERMS.md | ||
| UX_RECOMMENDATIONS.md | ||
Nib
A private, offline-first Android publishing client for Ghost.
Write a quick note or a full post, attach media, manage your publication, and publish directly to your own Ghost site. Nib has no account or backend of its own: drafts stay on the device, credentials stay encrypted on the device, and publishing traffic goes straight to Ghost.
Product tour
Ghost publishing tools
- Gift links for published members-only posts and pages.
- Ghost-rendered newsletter previews and explicitly confirmed test emails.
- Correct tracked link destinations after a newsletter has been sent.
- Manage outgoing recommendations and browse publications recommending you.
- Inspect welcome-email sequences, per-email performance and link clicks.
Availability depends on the Ghost version, enabled features and credential permissions. Automations are read-only and require Ghost's Automations beta. See contracts and limitations.
Publish without losing ownership of your work
- Offline-first queue - every post is persisted to Room before WorkManager attempts a Ghost upload, with account-scoped retries and exponential backoff.
- Body-safe editing - metadata-only edits do not flatten formatting created in Ghost Admin. Nib withholds a body it cannot represent faithfully instead of guessing.
- Duplicate-publish protection - interrupted creates are reconciled before retry, including newsletter sends and posts whose upload result was lost.
- Bodies are written in Lexical, the format Ghost 5+ stores, so what Nib sends is what
Ghost keeps. Node shapes were verified against a live Ghost rather than taken from a schema; see
docs/LEXICAL.md. Rows queued under the previous format are finished in it. - Simple or advanced composer, chosen per blog. Simple is a plain text field and nothing else; advanced adds the formatting toolbar, blocks and cards.
- Rich post settings - optional title, excerpt, visibility, slug, canonical URL, feature image, pinning, scheduling, and newsletter audience.
- Media and links - multiple images with independent feature/body roles, alt text and captions, plus video, audio, files, and Open Graph link previews.
- Formatting - opt-in bold, emphasis, links, and inline code, plus headings, blockquotes, bulleted and numbered lists, dividers, and fenced code blocks. All of it round-trips through Ghost, and none of it reinterprets a post written before the format existed.
- Share to Nib - accept text, selected text, one image, or multiple images from other Android apps and safely merge or replace the current draft.
- Private-first writing assistant - proofread, change tone, shorten, summarize, or suggest a title, excerpt and tags with Gemini Nano. On unsupported devices, a separately labelled action can send the draft to Google Gemini with your own API key. Every result is a diff you explicitly apply or discard; cloud fallback is never automatic.
- Optional alt-text assistant - generate a reviewable suggestion locally with Gemini Nano; sending a metadata-free prepared image through your own Gemini API key is a separate fallback.
- System dictation - asks Android's installed recognizer for offline operation and always puts the returned text through review before it enters the draft.
Run a Ghost publication from Android
- Editorial timeline with title-aware cards, search history scoped per blog, status filters, sorting, swipe actions, and queued-state visibility.
- Manage hub for pages, tags, staff, members, newsletters, tiers, and offers. Supported records can be edited in Nib; read-only commerce and staff data stay explicitly read-only.
- Statistics across every page returned by Ghost: post counts, words, reading time, and tag breakdowns, with incomplete-data states surfaced instead of hidden.
- Ghost handoff for bodies that need the full Ghost editor.
- Demo mode for exploring the app without Ghost credentials or Ghost API traffic.
Feel native on Android
- Three account-safe home-screen widgets: blog shortcut, per-blog statistics, and all-blogs overview.
- Dynamic per-blog launcher shortcuts and 40 launcher icon designs with failure reporting and startup reconciliation.
- Material 3 UI with Light, Dark, and System modes; free Dynamic/Material You colour; and Pro preset accents independent of light/dark mode.
- Large-font handling, state restoration, accessible names and live regions are covered on critical Compose surfaces by Robolectric render tests.
- A one-time What's New dialog summarizes skipped releases and can be reopened from Settings.
- Send feedback from Settings picks a category and opens a pre-filled mail carrying the build and the device only, never a blog URL, an API key, or post content.
Free and Pro
Core publishing is not paywalled. Writing, editing, deleting, drafts, inbound sharing, the offline queue, statistics, the alt-text assistant, and access to existing data remain free.
| Capability | Free | Nib Pro |
|---|---|---|
| Write, edit, publish, delete, share, and queue offline | Yes | Yes |
| Images, other media, link previews, hashtags, and statistics | Yes | Yes |
| Connected Ghost blogs | 1 | Up to 5 |
| Scheduled publishing and newsletter sending | - | Yes |
| Members, pages, and full tag management | - | Yes |
| Author assignment, Ghost revisions, and reusable post templates | - | Yes |
| Dynamic/Material You colour | Yes | Yes |
| Preset accent colours and per-account emoji | - | Yes |
Nib Pro is available as monthly or yearly access under the nib_pro subscription, or as the
one-time nib_pro_lifetime purchase. Prices and purchase handling come from Google Play. Anyone
who installed Nib before versionCode 10, the first enforcing build, keeps the capabilities they
already had permanently.
The entitlement model, failure behavior, and product invariants are documented in MONETIZATION.md. Manual Play release gates live in PLAY_CONSOLE_CHECKLIST.md.
Privacy and security
- Ghost Admin credentials and the optional Gemini key are stored with Android encrypted preferences backed by Android Keystore.
- Accounts are isolated across credentials, local posts, queues, preferences, widgets, and shortcuts. Retrofit clients are keyed by normalized Ghost URL and credential fingerprint.
- Release builds block cleartext traffic and do not log authorization headers, raw Ghost responses, purchase tokens, order IDs, or post content.
- Nib has no advertising SDK and deliberately excludes Firebase Analytics. Local monetization counters never leave the device.
- Firebase Crashlytics reporting is on by default and can be switched off immediately in Settings. A refusal survives app launches and account disconnection, and Nib exposes no API for attaching Ghost or user content to a crash report.
- Link previews, remote member avatars, Google Play Billing, Crashlytics, and the explicitly requested Gemini alt-text flow involve services other than the configured Ghost site. Their exact data flows and deletion behavior are described in PRIVACY.md.
Architecture
Nib is a single-module Kotlin application using MVVM with repositories. Room owns durable local post and queue state; Retrofit talks to the Ghost Admin API; WorkManager owns deferred publishing.
flowchart LR
UI["Jetpack Compose UI"] --> VM["ViewModels / StateFlow"]
VM --> REPO["Repositories"]
REPO --> ROOM["Room v13"]
ROOM --> WORK["Account-scoped WorkManager queue"]
WORK --> GHOST["Ghost Admin API"]
REPO <--> GHOST
com.nib.microblog/
├── data/
│ ├── api/ Retrofit, JWT authentication, safe API errors
│ ├── db/ Room database, DAOs, explicit migrations
│ ├── icon/ Launcher icon switching
│ ├── repository/ Ghost repositories and link-preview fetching
│ ├── shortcuts/ Account-targeted launcher shortcuts
│ └── widget/ Credential-free widget snapshots and bindings
├── diagnostics/ Opt-out Crashlytics boundary
├── inference/ Optional Gemini alt-text provider and image preparation
├── monetization/ Play Billing, entitlement resolution, ProGate
├── ui/ Compose product surfaces and navigation
└── worker/ Publishing and widget-refresh workers
The most important engineering invariants are intentional and regression-tested:
- every database, repository, queue, worker, widget, and shortcut operation stays scoped by
accountId; - Room migrations preserve data and never fall back to destructive migration;
- a queued create/update carries enough ownership and reconciliation state to survive retries, process death, concurrent edits, and Ghost 5+ returning HTML instead of mobiledoc;
- credentials are removed durably and a recoverable encrypted-store failure cannot wipe other credential stores;
- release shrinking keeps the generic coroutine signatures Retrofit reads at runtime.
Repository-specific implementation constraints are maintained in AGENTS.md, the single
guide every coding agent reads; CLAUDE.md only imports it.
Project status
| Item | Current repository state |
|---|---|
| App version | 1.2 (versionCode 70) |
| Android | min SDK 26 (Android 8.0), target SDK 36, compile SDK 37 |
| Database | Room schema v13 with exported schemas and explicit migrations |
| Toolchain | JDK 21, Gradle 9.6.1, AGP 9.3.0, Kotlin 2.3.21, KSP 2.3.9 |
| Automated gate | Debug and release unit tests; Android lint; debug APK, minified release APK, and release AAB builds |
| Release artifacts | Debug APK, minified release APK, and release AAB; release is unsigned unless signing is configured |
The automated gate runs on pushes and pull requests through
.github/workflows/gate.yml. It intentionally tests both debug and
release variants because an R8 regression previously broke Ghost calls only in the minified
artifact. The gate also probes the release APK's emitted DEX signatures. Instrumented/device
checks, Play Billing scenarios, and store declarations remain manual release gates.
Open work is tracked in GitHub Issues. BACKLOG.md is a generated, checkout-friendly index of those issues; do not edit its generated section by hand. Audit evidence and known manual QA gaps live in AUDIT_REPORT.md, UX_RECOMMENDATIONS.md, and CHANGELOG_AGENT.md.
Getting started
Requirements
- A current Android Studio release with Android SDK 37 installed
- JDK 21; JDK 26 is not supported by the current Gradle/AGP toolchain
- A self-hosted or Ghost(Pro) publication with Admin API access, unless you use demo mode
Connect a Ghost publication
- Open Ghost Admin -> Settings -> Integrations.
- Create a Custom Integration.
- Copy its Admin API key in
id:secretformat. - Start Nib and enter the publication URL and key in the setup flow.
Nib hex-decodes the secret and signs five-minute HS256 Admin API JWTs on the device. The Admin API key never leaves the device; only the short-lived JWT is sent to the configured Ghost site.
Build and run
git clone https://github.com/pawelorzech/Nib.git
cd Nib
export JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home
./gradlew app:assembleDebug
Install app/build/outputs/apk/debug/app-debug.apk on a device or emulator, then connect a Ghost
publication or choose Explore demo. If app/google-services.json is absent, the build still
succeeds and crash reporting is disabled for that artifact.
Build, test, and release
./gradlew app:assembleDebug # Debug APK
./gradlew app:testDebugUnitTest # Debug unit tests
./gradlew app:testReleaseUnitTest # Release unit tests
./gradlew test # Both unit-test variants
./gradlew app:lintDebug # Android lint
./gradlew app:assembleRelease # Minified release APK
./gradlew app:bundleRelease # Release AAB
Robolectric tests include Android resources. The suite covers authentication, account isolation, Room migrations, body ownership, publishing reconciliation, share-payload lifetime, media upload recovery, billing, diagnostics consent, accessibility semantics, state restoration, and Compose render policies.
After changing dependencies or proguard-rules.pro, run the artifact-level Retrofit signature
probe documented in AGENTS.md. Unit tests execute unminified bytecode and cannot
detect that class of shrinker damage.
Sign a release
Release builds stay unsigned until all four values are supplied as nib.* Gradle properties or
their NIB_* environment-variable equivalents:
nib.storeFile=/absolute/path/to/nib-release.keystore
nib.storePassword=...
nib.keyAlias=nib
nib.keyPassword=...
Generate a keystore once if needed:
keytool -genkeypair -v -keystore nib-release.keystore \
-alias nib -keyalg RSA -keysize 2048 -validity 10000
Never commit the keystore or signing values.
Tech stack
| Layer | Technology |
|---|---|
| UI | Jetpack Compose, Material 3 |
| State and navigation | StateFlow, Compose Navigation, MVVM |
| Networking | Retrofit 2, OkHttp 4, Gson |
| Local data | Room 2.7.2, schema v13 |
| Background work | WorkManager |
| Images and media | Coil, Media3 ExoPlayer |
| Authentication | JJWT HS256, EncryptedSharedPreferences |
| Monetization | Google Play Billing 9.1.0 |
| Diagnostics | Firebase Crashlytics without Firebase Analytics |
| Testing | JUnit 4, Robolectric 4.16.1, MockWebServer, Compose UI tests |
Documentation
- CHANGELOG.md - user-visible release history
- STORE_LISTING.md - Google Play copy and asset inventory
- PRIVACY.md and TERMS.md - user-facing policies
- MONETIZATION.md - Free/Pro architecture and safety model
- PLAY_CONSOLE_CHECKLIST.md - manual release operations
- CONTRIBUTING.md - contribution workflow
License
Nib is licensed under the PolyForm Noncommercial License 1.0.0. You may use, modify, and share it for non-commercial purposes. Commercial use requires permission from the author.
Built by Paweł Orzech - orzech.me.