rename: Android is Inkwell — package, applicationId, uniffi class, assets

Step 4 of milestone 481 (Scribe note 5071: a full rename).

- namespace and applicationId com.fabledsword.inkwell; the Kotlin package moves
  with them, and ktlint re-sorted the imports the rename reordered (checked
  locally with CI's ktlint 1.4.0 and detekt 1.23.7, both clean)
- uniffi: class Inkwell in com.fabledsword.inkwell.core, InkwellApplication,
  InkwellTheme, Theme.Inkwell, log tags, prefs and work names, client agent
  inkwell-android
- the lane publishes inkwell.apk / inkwell-android.json; fetch-clients,
  guard-forward, publish-release and write-manifest read the same names

A new applicationId is a new app. The old ThoughtSync app keeps its own store
and stays installed beside it. Notes cross over by syncing, and the old app is
then removed by hand.

Kept: the signing keyAlias is still "thoughtsync". It names the key inside the
existing keystore, and the key, and so the certificate, are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 14:34:02 -04:00
co-authored by Claude Opus 5.5
parent 256fba3610
commit 81cd719327
55 changed files with 222 additions and 219 deletions
+19 -19
View File
@@ -7,7 +7,7 @@
//!
//! ## Shape
//!
//! One `ThoughtSync` object holds the store and the blob directory, mirroring how
//! One `Inkwell` object holds the store and the blob directory, mirroring how
//! Tauri manages them as app state. Kotlin constructs it once, keeps it for the
//! process lifetime, and calls methods on it.
//!
@@ -108,13 +108,13 @@ impl CoreError {
/// behind its mutex, the blob store being a path — which is what lets uniffi share
/// one instance across coroutines.
#[derive(uniffi::Object)]
pub struct ThoughtSync {
pub struct Inkwell {
db: Db,
blobs: BlobStore,
}
#[uniffi::export]
impl ThoughtSync {
impl Inkwell {
/// Open (creating on first run) the store under `data_dir`, and the attachment
/// directory beside it.
///
@@ -131,7 +131,7 @@ impl ThoughtSync {
log::info!("local store ready — {}", local::summary(&db));
let blobs = BlobStore::new(dir.join("blobs")).map_err(CoreError::store)?;
Ok(Arc::new(ThoughtSync { db, blobs }))
Ok(Arc::new(Inkwell { db, blobs }))
}
/// A one-line count summary, for the boot log.
@@ -413,7 +413,7 @@ impl ThoughtSync {
/// functions. Split into its own impl block so the runtime attribute — and the fact
/// that everything in here touches the network — is visible at a glance.
#[uniffi::export(async_runtime = "tokio")]
impl ThoughtSync {
impl Inkwell {
/// Ask a server who it is, without committing to anything. Called as the user
/// finishes typing an address, so they see what answered before handing over
/// credentials.
@@ -525,7 +525,7 @@ impl ThoughtSync {
///
/// Takes the destination rather than choosing one: only Android knows a
/// directory its own package installer can read from, and the core has no
/// business guessing at platform paths — the same reason `ThoughtSync::new`
/// business guessing at platform paths — the same reason `Inkwell::new`
/// takes a data dir.
pub async fn download_client_update(&self, dest_path: String) -> Result<(), CoreError> {
let (base_url, token) = self.credentials()?;
@@ -587,7 +587,7 @@ pub fn checklist_render(text: String, checked: bool) -> String {
/// stamps and nobody has seen. The honest value is the installed package's own
/// `versionName`, which is what Kotlin passes here.
///
/// Called once from `ThoughtSyncApplication.onCreate`, before anything can sync.
/// Called once from `InkwellApplication.onCreate`, before anything can sync.
#[uniffi::export]
pub fn set_client_agent(name: String, version: String) {
compat::set_client_agent(&name, &version);
@@ -620,7 +620,7 @@ pub fn body_tags(body: String) -> Vec<BodyTag> {
/// Helpers, deliberately NOT exported — uniffi only binds what an `#[uniffi::export]`
/// block names, so these stay Rust-side.
impl ThoughtSync {
impl Inkwell {
/// Apply a `{text}` or `{checked}` patch to one checklist item.
///
/// The two public setters differ only in the key they write, and the lock +
@@ -701,7 +701,7 @@ mod tests {
#[test]
fn creates_a_store_and_round_trips_a_note() {
let dir = scratch_dir();
let app = ThoughtSync::new(dir.clone()).expect("a fresh data dir should open");
let app = Inkwell::new(dir.clone()).expect("a fresh data dir should open");
let created = app
.create_note(draft("Groceries\nmilk"))
@@ -723,7 +723,7 @@ mod tests {
#[test]
fn a_note_is_named_by_its_first_line() {
let dir = scratch_dir();
let app = ThoughtSync::new(dir.clone()).expect("a fresh data dir should open");
let app = Inkwell::new(dir.clone()).expect("a fresh data dir should open");
let created = app
.create_note(draft("just a thought"))
@@ -738,7 +738,7 @@ mod tests {
#[test]
fn a_note_with_only_items_is_named_by_its_first_item() {
let dir = scratch_dir();
let app = ThoughtSync::new(dir.clone()).expect("a fresh data dir should open");
let app = Inkwell::new(dir.clone()).expect("a fresh data dir should open");
let created = app
.create_note(NoteDraft {
@@ -757,7 +757,7 @@ mod tests {
#[test]
fn syncing_unlinked_reports_not_linked() {
let dir = scratch_dir();
let app = ThoughtSync::new(dir.clone()).expect("a fresh data dir should open");
let app = Inkwell::new(dir.clone()).expect("a fresh data dir should open");
let status = app.sync_status().expect("status should read");
assert!(!status.linked);
@@ -774,7 +774,7 @@ mod tests {
#[test]
fn checklist_items_can_be_added_ticked_retitled_and_removed() {
let dir = scratch_dir();
let app = ThoughtSync::new(dir.clone()).expect("a fresh data dir should open");
let app = Inkwell::new(dir.clone()).expect("a fresh data dir should open");
let note = app
.create_note(NoteDraft {
body: "Packing".to_string(),
@@ -828,7 +828,7 @@ mod tests {
#[test]
fn setting_labels_leaves_tag_derived_ones_alone() {
let dir = scratch_dir();
let app = ThoughtSync::new(dir.clone()).expect("a fresh data dir should open");
let app = Inkwell::new(dir.clone()).expect("a fresh data dir should open");
let note = app
.create_note(draft("Trip\nbook the ferry #travel"))
@@ -869,7 +869,7 @@ mod tests {
#[test]
fn deleting_forever_removes_the_note() {
let dir = scratch_dir();
let app = ThoughtSync::new(dir.clone()).expect("a fresh data dir should open");
let app = Inkwell::new(dir.clone()).expect("a fresh data dir should open");
let note = app.create_note(draft("Ephemeral\nbody")).expect("create");
app.delete_note_forever(note.id.clone())
@@ -886,7 +886,7 @@ mod tests {
#[test]
fn reminders_can_be_snoozed_and_completed() {
let dir = scratch_dir();
let app = ThoughtSync::new(dir.clone()).expect("a fresh data dir should open");
let app = Inkwell::new(dir.clone()).expect("a fresh data dir should open");
let note = app.create_note(draft("Call back")).expect("create");
assert_eq!(note.remind_at, None);
@@ -912,7 +912,7 @@ mod tests {
#[test]
fn completing_a_recurring_reminder_moves_it_rather_than_ending_it() {
let dir = scratch_dir();
let app = ThoughtSync::new(dir.clone()).expect("a fresh data dir should open");
let app = Inkwell::new(dir.clone()).expect("a fresh data dir should open");
let note = app.create_note(draft("Water the plants")).expect("create");
let armed = app
@@ -974,7 +974,7 @@ mod tests {
#[test]
fn renaming_onto_an_existing_tag_merges_into_the_older_one() {
let dir = scratch_dir();
let app = ThoughtSync::new(dir.clone()).expect("a fresh data dir should open");
let app = Inkwell::new(dir.clone()).expect("a fresh data dir should open");
let older = app.create_label("grocery".to_string()).expect("older");
// `created_at` is RFC3339 to the MILLISECOND. Without a gap the two rows can
@@ -1019,7 +1019,7 @@ mod tests {
#[test]
fn the_rename_merge_survivor_does_not_depend_on_the_direction() {
let dir = scratch_dir();
let app = ThoughtSync::new(dir.clone()).expect("a fresh data dir should open");
let app = Inkwell::new(dir.clone()).expect("a fresh data dir should open");
let older = app.create_label("grocery".to_string()).expect("older");
std::thread::sleep(std::time::Duration::from_millis(5));