2 Commits
Author SHA1 Message Date
bvandeusenandClaude Opus 5.5 6d082b2ad8 rename: the docs say Inkwell, and nothing else still says ThoughtSync by accident
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 16s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 4s
Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
CI & Build / Python tests (push) Successful in 17s
CI & Build / Build & push image (push) Skipped
CI & Build / integration (push) Successful in 50s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m14s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 6m2s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 8m36s
Step 6 of milestone 481. README, docs/*, ci-requirements.md, the desktop and
Arch READMEs, alembic.ini, .gitignore, the frontend package name, the service
worker's cache name (its activate handler deletes any cache by another name, so
the old one is cleaned up), and the Android names in the release body.

What still says thoughtsync does so on purpose (Scribe note 5071):
- the desktop data crossover (crossover.rs) and its startup log
- the old-export import marker
- the "Upgrading from ThoughtSync" block in .env.example, and compose's pointer
  to it
- the packages being retired: deb conflicts/replaces thought-sync, pacman
  thoughtsync and thoughtsync-desktop
- the Android signing keyAlias, which names a key in the existing keystore
- history: shipped alembic migrations, and the test-binary hashes that
  ci-requirements.md records from 2026-08-18

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 14:35:02 -04:00
bvandeusenandClaude Opus 5.5 81cd719327 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>
2026-10-06 14:34:02 -04:00
67 changed files with 289 additions and 284 deletions
+13 -13
View File
@@ -134,7 +134,7 @@ jobs:
echo "code=$code" >> $GITHUB_OUTPUT
if [ -n "${ANDROID_KEYSTORE_BASE64:-}" ]; then
printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 -d > /tmp/thoughtsync-release.jks
printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 -d > /tmp/inkwell-release.jks
echo "variant=Release" >> $GITHUB_OUTPUT
echo "label=release" >> $GITHUB_OUTPUT
# DEBUG profile, in a release APK, deliberately — see the note above
@@ -143,7 +143,7 @@ jobs:
# outright (run 4077). Unpicking that is worth doing and is not worth
# blocking signed builds on.
echo "profile=debug" >> $GITHUB_OUTPUT
echo "keystore=/tmp/thoughtsync-release.jks" >> $GITHUB_OUTPUT
echo "keystore=/tmp/inkwell-release.jks" >> $GITHUB_OUTPUT
echo "apk=android/app/build/outputs/apk/release/app-release.apk" >> $GITHUB_OUTPUT
echo "Signed release build — $version (versionCode $code)"
else
@@ -167,7 +167,7 @@ jobs:
# from the built .so. Run as its own step so a Rust failure is legible as a
# Rust failure instead of arriving inside a Gradle stack trace.
- name: Build the native library and bindings
run: ./gradlew generateUniffiBindings -PTHOUGHTSYNC_CARGO_PROFILE=${{ steps.build.outputs.profile }}
run: ./gradlew generateUniffiBindings -PINKWELL_CARGO_PROFILE=${{ steps.build.outputs.profile }}
# The image's PINNED CLIs, not Gradle plugins. ci-rust-android carries both
# (M12 step 3) precisely so this lane needs no second image, and going
@@ -193,7 +193,7 @@ jobs:
# not exist (run 4082). It costs one extra Kotlin compile and buys the
# type-check on the debug variant, which is the one an emulator build
# would use.
run: ./gradlew testDebugUnitTest -PTHOUGHTSYNC_CARGO_PROFILE=${{ steps.build.outputs.profile }}
run: ./gradlew testDebugUnitTest -PINKWELL_CARGO_PROFILE=${{ steps.build.outputs.profile }}
- name: Assemble the APK
env:
@@ -203,9 +203,9 @@ jobs:
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
run: |
./gradlew assemble${{ steps.build.outputs.variant }} \
-PTHOUGHTSYNC_CARGO_PROFILE=${{ steps.build.outputs.profile }} \
-PTHOUGHTSYNC_VERSION_NAME=${{ steps.build.outputs.name }} \
-PTHOUGHTSYNC_VERSION_CODE=${{ steps.build.outputs.code }}
-PINKWELL_CARGO_PROFILE=${{ steps.build.outputs.profile }} \
-PINKWELL_VERSION_NAME=${{ steps.build.outputs.name }} \
-PINKWELL_VERSION_CODE=${{ steps.build.outputs.code }}
# Prints the certificate the APK was actually signed with, so the operator
# can compare it against the fingerprint recorded when the key was
@@ -226,10 +226,10 @@ jobs:
if: steps.build.outputs.keystore != ''
run: |
mkdir -p dist
cp "app/build/outputs/apk/release/app-release.apk" dist/thoughtsync.apk
size="$(wc -c < dist/thoughtsync.apk | tr -d ' ')"
sha="$(sha256sum dist/thoughtsync.apk | cut -d' ' -f1)"
cat > dist/thoughtsync-android.json <<JSON
cp "app/build/outputs/apk/release/app-release.apk" dist/inkwell.apk
size="$(wc -c < dist/inkwell.apk | tr -d ' ')"
sha="$(sha256sum dist/inkwell.apk | cut -d' ' -f1)"
cat > dist/inkwell-android.json <<JSON
{
"version_name": "${{ steps.build.outputs.name }}",
"version_code": ${{ steps.build.outputs.code }},
@@ -237,7 +237,7 @@ jobs:
"sha256": "$sha"
}
JSON
cat dist/thoughtsync-android.json
cat dist/inkwell-android.json
# The rolling channel for this branch, the same fixed-tag releases the desktop
# bundles use. CI artifacts are per-run and auth-gated, so they are no use as a
@@ -276,7 +276,7 @@ jobs:
with:
# The APK's variant, NOT the Cargo profile — those are the same word
# for different things and the profile is pinned to debug (#2810).
name: thoughtsync-android-${{ steps.build.outputs.label }}-${{ github.sha }}
name: inkwell-android-${{ steps.build.outputs.label }}-${{ github.sha }}
path: ${{ steps.build.outputs.apk }}
if-no-files-found: error
+2 -2
View File
@@ -209,5 +209,5 @@ android/local.properties
# The Android client CI bakes into the server image. Fetched fresh on every image
# build, so it is never worth 55 MiB of git history. client/.keep IS tracked, so
# the Dockerfile's COPY always has a directory to copy.
client/thoughtsync.apk
client/thoughtsync-android.json
client/inkwell.apk
client/inkwell-android.json
+14 -14
View File
@@ -1,4 +1,4 @@
# ThoughtSync
# Inkwell
Self-hosted personal thought-capture web app in the **FabledSword** family — a
Google-Keep-style **masonry post-it board** for capturing disparate thoughts in
@@ -23,7 +23,7 @@ docker-compose.yml local app + Postgres stack
## Development
Backend (needs a Postgres reachable at `THOUGHTSYNC_DATABASE_URL`):
Backend (needs a Postgres reachable at `INKWELL_DATABASE_URL`):
```sh
pip install -e ".[dev]"
@@ -64,40 +64,40 @@ services:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: thoughtsync
POSTGRES_USER: inkwell
POSTGRES_PASSWORD: CHANGE_ME # change this
POSTGRES_DB: thoughtsync
POSTGRES_DB: inkwell
volumes:
- thoughtsync-db:/var/lib/postgresql/data
- inkwell-db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U thoughtsync"]
test: ["CMD-SHELL", "pg_isready -U inkwell"]
interval: 5s
timeout: 5s
retries: 10
app:
image: git.fabledsword.com/bvandeusen/thoughtsync:latest # :dev for the current dev build
image: git.fabledsword.com/bvandeusen/inkwell:latest # :dev for the current dev build
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
THOUGHTSYNC_DATABASE_URL: postgresql+asyncpg://thoughtsync:CHANGE_ME@db:5432/thoughtsync
INKWELL_DATABASE_URL: postgresql+asyncpg://inkwell:CHANGE_ME@db:5432/inkwell
volumes:
- thoughtsync-data:/var/thoughtsync # uploaded images; omit if you don't use attachments
- inkwell-data:/var/inkwell # uploaded images; omit if you don't use attachments
ports:
- "5000:5000"
volumes:
thoughtsync-db:
thoughtsync-data:
inkwell-db:
inkwell-data:
```
Then open `http://<host>:5000` and register — **the first account becomes the admin**.
- **Only `THOUGHTSYNC_DATABASE_URL` is required.** `THOUGHTSYNC_SECRET_KEY` is optional; if
- **Only `INKWELL_DATABASE_URL` is required.** `INKWELL_SECRET_KEY` is optional; if
unset, a signing key is generated and persisted in the database (sessions survive restarts).
- Uploaded images live under the `thoughtsync-data` volume at `/var/thoughtsync`.
- Uploaded images live under the `inkwell-data` volume at `/var/inkwell`.
- The app waits for the database and runs migrations (`alembic upgrade head`) automatically on start.
- **Image tags:** `:latest` (stable, built from `main`) · `:dev` (latest `dev`
build) · `:<git-sha>` on `main` only (immutable, the rollback unit). There are
@@ -108,7 +108,7 @@ Then open `http://<host>:5000` and register — **the first account becomes the
port, and back up the attachment volume as well as the database. See
[docs/public-hosting.md](docs/public-hosting.md), which also lists what the app
hardens on its own and what it deliberately doesn't.
- **Install as an app (PWA):** ThoughtSync is installable ("Add to Home Screen" / the
- **Install as an app (PWA):** Inkwell is installable ("Add to Home Screen" / the
browser's install button) for an app-like window. Browsers only offer install over a
**secure context**, so put the app behind a reverse proxy terminating **HTTPS** (or reach
it via `localhost`) — plain `http://<host>:5000` won't show the install prompt.
+1 -1
View File
@@ -1,4 +1,4 @@
# Alembic single-database async configuration for ThoughtSync.
# Alembic single-database async configuration for Inkwell.
[alembic]
script_location = %(here)s/alembic
+9 -6
View File
@@ -141,7 +141,7 @@ val rustInputs =
* build, and neither is worth holding signed APKs up for. Scribe #2810.
*/
val rustProfile =
(project.findProperty("THOUGHTSYNC_CARGO_PROFILE") as String?)?.takeIf { it.isNotBlank() }
(project.findProperty("INKWELL_CARGO_PROFILE") as String?)?.takeIf { it.isNotBlank() }
?: "debug"
val jniLibsOut = layout.buildDirectory.dir("rustJniLibs")
@@ -169,11 +169,11 @@ val generateBindings =
}
android {
namespace = "com.fabledsword.thoughtsync"
namespace = "com.fabledsword.inkwell"
compileSdk = 36
defaultConfig {
applicationId = "com.fabledsword.thoughtsync"
applicationId = "com.fabledsword.inkwell"
// 26 (Android 8, 2017) matches Minstrel and clears the NDK's floor with
// room to spare.
minSdk = 26
@@ -181,9 +181,9 @@ android {
// Injected by CI from the git tag + commit count for a release; "dev"
// locally so the About screen reads honestly rather than claiming 1.0.
val nameOverride =
(project.findProperty("THOUGHTSYNC_VERSION_NAME") as String?)?.takeIf { it.isNotBlank() }
(project.findProperty("INKWELL_VERSION_NAME") as String?)?.takeIf { it.isNotBlank() }
val codeOverride =
(project.findProperty("THOUGHTSYNC_VERSION_CODE") as String?)?.toIntOrNull()
(project.findProperty("INKWELL_VERSION_CODE") as String?)?.toIntOrNull()
versionCode = codeOverride ?: 1
versionName = nameOverride ?: "dev"
@@ -215,7 +215,10 @@ android {
// Hardcoded, and NOT a secret: the alias is fixed for the life of
// this app and is written into the certificate every install
// already carries. Hiding it would buy nothing and stop this file
// describing its own signing setup.
// describing its own signing setup. It still says `thoughtsync`
// after the rename to Inkwell, because it names the key inside the
// existing keystore, and renaming it would only stop that key being
// found. Same key, so the signing certificate did not change either.
keyAlias = "thoughtsync"
// PKCS12 cannot hold a key password distinct from the store
// password — keytool refuses to set one — so this is the same
+1 -1
View File
@@ -4,4 +4,4 @@
# is the worst possible time to learn it.
-keep class com.sun.jna.** { *; }
-keepclassmembers class * extends com.sun.jna.** { public *; }
-keep class com.fabledsword.thoughtsync.core.** { *; }
-keep class com.fabledsword.inkwell.core.** { *; }
+3 -3
View File
@@ -91,13 +91,13 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:name=".ThoughtSyncApplication"
android:name=".InkwellApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ThoughtSync"
android:theme="@style/Theme.Inkwell"
android:usesCleartextTraffic="true">
<!--
launchMode="singleTop" exists for the SHARE filters below.
@@ -115,7 +115,7 @@
android:exported="true"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize"
android:theme="@style/Theme.ThoughtSync">
android:theme="@style/Theme.Inkwell">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync
package com.fabledsword.inkwell
import android.content.Context
import android.content.Intent
@@ -42,7 +42,7 @@ import java.io.File
* [UpdateReceiver], which is why a failure can be shown rather than guessed at.
*/
object AppUpdate {
private const val TAG = "ThoughtSyncUpdate"
private const val TAG = "InkwellUpdate"
/** This build's versionCode — what the server's is compared against. */
fun installedVersionCode(context: Context): Long =
@@ -173,5 +173,5 @@ object AppUpdate {
.intentSender
}
private const val WRITE_NAME = "thoughtsync-update"
private const val WRITE_NAME = "inkwell-update"
}
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync
package com.fabledsword.inkwell
import android.content.Context
@@ -1,9 +1,9 @@
package com.fabledsword.thoughtsync
package com.fabledsword.inkwell
import android.app.Application
import android.util.Log
import com.fabledsword.thoughtsync.core.ThoughtSync
import com.fabledsword.thoughtsync.core.setClientAgent
import com.fabledsword.inkwell.core.Inkwell
import com.fabledsword.inkwell.core.setClientAgent
/**
* Opens the shared Rust core once, for the process lifetime.
@@ -16,14 +16,14 @@ import com.fabledsword.thoughtsync.core.setClientAgent
* removed on uninstall, and never on external media. The core does not guess at
* platform paths; Android is the only thing that knows where this is.
*/
class ThoughtSyncApplication : Application() {
class InkwellApplication : Application() {
/**
* Null only if the store could not be opened — a corrupt or unwritable
* database. The UI reports that honestly rather than crashing on first
* touch, because a user whose notes won't open needs a message, not a
* stack trace.
*/
var core: ThoughtSync? = null
var core: Inkwell? = null
private set
var openFailure: String? = null
@@ -34,13 +34,13 @@ class ThoughtSyncApplication : Application() {
// Introduce this app to any server it links to, before anything can sync.
// The core cannot name us — the same crate is compiled into the desktop app,
// and it used to announce every phone as `thoughtsync-desktop` carrying the
// and it used to announce every phone as `inkwell-desktop` carrying the
// core crate's own version. "unknown" rather than a guess when the package
// manager will not say (note 3127 §5).
setClientAgent("thoughtsync-android", installedVersionName() ?: "unknown")
setClientAgent("inkwell-android", installedVersionName() ?: "unknown")
try {
val handle = ThoughtSync(filesDir.absolutePath)
val handle = Inkwell(filesDir.absolutePath)
core = handle
Log.i(TAG, "local store ready — ${handle.summary()}")
} catch (e: Exception) {
@@ -53,6 +53,6 @@ class ThoughtSyncApplication : Application() {
}
private companion object {
const val TAG = "ThoughtSync"
const val TAG = "Inkwell"
}
}
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync
package com.fabledsword.inkwell
import android.Manifest
import android.content.Intent
@@ -22,22 +22,22 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel
import com.fabledsword.thoughtsync.core.ThoughtSync
import com.fabledsword.thoughtsync.ui.BoardScreen
import com.fabledsword.thoughtsync.ui.BoardSync
import com.fabledsword.thoughtsync.ui.BoardUpdate
import com.fabledsword.thoughtsync.ui.BoardViewModel
import com.fabledsword.thoughtsync.ui.ForegroundTransitions
import com.fabledsword.thoughtsync.ui.NoteEditorScreen
import com.fabledsword.thoughtsync.ui.StoreUnavailableScreen
import com.fabledsword.thoughtsync.ui.SyncScreen
import com.fabledsword.thoughtsync.ui.SyncState
import com.fabledsword.thoughtsync.ui.SyncViewModel
import com.fabledsword.thoughtsync.ui.TagsScreen
import com.fabledsword.thoughtsync.ui.TagsViewModel
import com.fabledsword.thoughtsync.ui.ThoughtSyncTheme
import com.fabledsword.thoughtsync.ui.UpdateViewModel
import com.fabledsword.thoughtsync.ui.olderThan
import com.fabledsword.inkwell.core.Inkwell
import com.fabledsword.inkwell.ui.BoardScreen
import com.fabledsword.inkwell.ui.BoardSync
import com.fabledsword.inkwell.ui.BoardUpdate
import com.fabledsword.inkwell.ui.BoardViewModel
import com.fabledsword.inkwell.ui.ForegroundTransitions
import com.fabledsword.inkwell.ui.InkwellTheme
import com.fabledsword.inkwell.ui.NoteEditorScreen
import com.fabledsword.inkwell.ui.StoreUnavailableScreen
import com.fabledsword.inkwell.ui.SyncScreen
import com.fabledsword.inkwell.ui.SyncState
import com.fabledsword.inkwell.ui.SyncViewModel
import com.fabledsword.inkwell.ui.TagsScreen
import com.fabledsword.inkwell.ui.TagsViewModel
import com.fabledsword.inkwell.ui.UpdateViewModel
import com.fabledsword.inkwell.ui.olderThan
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -68,12 +68,12 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
val app = application as ThoughtSyncApplication
val app = application as InkwellApplication
requestedNote.value = takeRequestedNote(intent)
sharedText.value = takeSharedText(intent)
setContent {
ThoughtSyncTheme {
InkwellTheme {
val core = app.core
if (core == null) {
// The store never opened. There is no board to show and no
@@ -174,7 +174,7 @@ private enum class Screen { BOARD, EDITOR, SYNC, TAGS }
*/
@Composable
private fun App(
core: ThoughtSync,
core: Inkwell,
requestedNote: MutableState<String?>,
sharedText: MutableState<String?>,
) {
@@ -360,7 +360,7 @@ private fun App(
* notifications this app would send — is spending it on nothing.
*/
@Composable
private fun ReminderAlarms(core: ThoughtSync) {
private fun ReminderAlarms(core: Inkwell) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
// Off the main thread: this reads every note that has a reminder, and a phone
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync
package com.fabledsword.inkwell
import android.app.NotificationChannel
import android.app.NotificationManager
@@ -8,7 +8,7 @@ import android.content.Intent
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.fabledsword.thoughtsync.core.Note
import com.fabledsword.inkwell.core.Note
/**
* What a due reminder looks like in the shade.
@@ -117,5 +117,5 @@ internal object ReminderNotification {
)
private const val CHANNEL = "reminders"
private const val TAG = "ThoughtSyncReminders"
private const val TAG = "InkwellReminders"
}
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync
package com.fabledsword.inkwell
import android.content.BroadcastReceiver
import android.content.Context
@@ -36,7 +36,7 @@ class ReminderReceiver : BroadcastReceiver() {
context: Context,
intent: Intent,
) {
val core = (context.applicationContext as? ThoughtSyncApplication)?.core ?: return
val core = (context.applicationContext as? InkwellApplication)?.core ?: return
val action = intent.action
val noteId = intent.getStringExtra(Reminders.EXTRA_NOTE_ID)
val app = context.applicationContext
@@ -64,9 +64,9 @@ class ReminderReceiver : BroadcastReceiver() {
}
companion object {
const val ACTION_DUE = "com.fabledsword.thoughtsync.REMINDER_DUE"
const val ACTION_DONE = "com.fabledsword.thoughtsync.REMINDER_DONE"
const val ACTION_SNOOZE = "com.fabledsword.thoughtsync.REMINDER_SNOOZE"
private const val TAG = "ThoughtSyncReminders"
const val ACTION_DUE = "com.fabledsword.inkwell.REMINDER_DUE"
const val ACTION_DONE = "com.fabledsword.inkwell.REMINDER_DONE"
const val ACTION_SNOOZE = "com.fabledsword.inkwell.REMINDER_SNOOZE"
private const val TAG = "InkwellReminders"
}
}
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync
package com.fabledsword.inkwell
import android.app.AlarmManager
import android.app.PendingIntent
@@ -7,8 +7,8 @@ import android.content.Intent
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationManagerCompat
import com.fabledsword.thoughtsync.core.Note
import com.fabledsword.thoughtsync.core.ThoughtSync
import com.fabledsword.inkwell.core.Inkwell
import com.fabledsword.inkwell.core.Note
import java.time.OffsetDateTime
/**
@@ -52,7 +52,7 @@ object Reminders {
private const val MISSED_WINDOW_MS = 24L * 60 * 60 * 1000
private const val SNOOZE_MINUTES = 60L
private const val TAG = "ThoughtSyncReminders"
private const val TAG = "InkwellReminders"
/**
* Announce what is due, then schedule the next one.
@@ -63,7 +63,7 @@ object Reminders {
*/
fun refresh(
context: Context,
core: ThoughtSync,
core: Inkwell,
) {
ReminderNotification.ensureChannel(context)
val notes =
@@ -111,7 +111,7 @@ object Reminders {
*/
fun promptToNotifyDue(
context: Context,
core: ThoughtSync,
core: Inkwell,
): Boolean =
!Announced(context).askedToNotify &&
!NotificationManagerCompat.from(context).areNotificationsEnabled() &&
@@ -123,7 +123,7 @@ object Reminders {
/** Clear the reminder, as the notification's Done action. */
fun complete(
context: Context,
core: ThoughtSync,
core: Inkwell,
noteId: String,
) {
runCatching { core.completeReminder(noteId) }
@@ -134,7 +134,7 @@ object Reminders {
/** Push the reminder an hour out, as the notification's Snooze action. */
fun snooze(
context: Context,
core: ThoughtSync,
core: Inkwell,
noteId: String,
) {
runCatching { core.snoozeReminder(noteId, SNOOZE_MINUTES) }
@@ -237,7 +237,7 @@ private class Announced(
fun markAsked() = prefs.edit().putBoolean(KEY_ASKED, true).apply()
private companion object {
const val FILE = "thoughtsync-reminders"
const val FILE = "inkwell-reminders"
const val KEY_SEEN = "announced"
const val KEY_PRIMED = "primed"
const val KEY_ASKED = "asked_to_notify"
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync
package com.fabledsword.inkwell
import android.content.Context
import androidx.work.BackoffPolicy
@@ -85,8 +85,8 @@ object SyncSchedule {
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
private const val PERIODIC = "thoughtsync-periodic-sync"
private const val PUSH = "thoughtsync-push-pending"
private const val PERIODIC = "inkwell-periodic-sync"
private const val PUSH = "inkwell-push-pending"
/** WorkManager's own minimum for periodic work. Asking for less gets this. */
private const val PERIOD_MINUTES = 15L
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync
package com.fabledsword.inkwell
import android.content.Context
@@ -38,7 +38,7 @@ class SyncSettings(
}
private companion object {
const val FILE = "thoughtsync-sync"
const val FILE = "inkwell-sync"
const val KEY_AUTOMATIC = "automatic"
}
}
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync
package com.fabledsword.inkwell
import android.content.Context
import android.util.Log
@@ -8,7 +8,7 @@ import androidx.work.WorkerParameters
/**
* One sync cycle, run by the system rather than by a person.
*
* WorkManager may start the process to run this, which means [ThoughtSyncApplication.onCreate]
* WorkManager may start the process to run this, which means [InkwellApplication.onCreate]
* has already opened the store by the time [doWork] is called — the same handle
* the UI uses, so there is never a second SQLite connection racing the first.
*
@@ -30,7 +30,7 @@ class SyncWorker(
params: WorkerParameters,
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val core = (applicationContext as? ThoughtSyncApplication)?.core
val core = (applicationContext as? InkwellApplication)?.core
// Both of these are "nothing to do", not "something went wrong", so both
// report success and let the run retire quietly:
@@ -67,6 +67,6 @@ class SyncWorker(
}
private companion object {
const val TAG = "ThoughtSyncWorker"
const val TAG = "InkwellWorker"
}
}
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync
package com.fabledsword.inkwell
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync
package com.fabledsword.inkwell
import android.content.BroadcastReceiver
import android.content.Context
@@ -71,7 +71,7 @@ class UpdateReceiver : BroadcastReceiver() {
}
companion object {
const val ACTION_INSTALLED = "com.fabledsword.thoughtsync.UPDATE_INSTALLED"
private const val TAG = "ThoughtSyncUpdate"
const val ACTION_INSTALLED = "com.fabledsword.inkwell.UPDATE_INSTALLED"
private const val TAG = "InkwellUpdate"
}
}
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Arrangement
@@ -33,7 +33,7 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import com.fabledsword.thoughtsync.R
import com.fabledsword.inkwell.R
/**
* The note's body, as fields and checkboxes rather than as markup.
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -63,9 +63,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import com.fabledsword.thoughtsync.R
import com.fabledsword.thoughtsync.core.Label
import com.fabledsword.thoughtsync.core.Note
import com.fabledsword.inkwell.R
import com.fabledsword.inkwell.core.Label
import com.fabledsword.inkwell.core.Note
import kotlinx.coroutines.launch
@Composable
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -6,12 +6,12 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.fabledsword.thoughtsync.core.Label
import com.fabledsword.thoughtsync.core.Note
import com.fabledsword.thoughtsync.core.NoteDraft
import com.fabledsword.thoughtsync.core.NoteEdit
import com.fabledsword.thoughtsync.core.NoteQuery
import com.fabledsword.thoughtsync.core.ThoughtSync
import com.fabledsword.inkwell.core.Inkwell
import com.fabledsword.inkwell.core.Label
import com.fabledsword.inkwell.core.Note
import com.fabledsword.inkwell.core.NoteDraft
import com.fabledsword.inkwell.core.NoteEdit
import com.fabledsword.inkwell.core.NoteQuery
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@@ -99,7 +99,7 @@ data class BoardState(
*/
@Suppress("TooManyFunctions")
class BoardViewModel(
private val core: ThoughtSync,
private val core: Inkwell,
/**
* Called after any write that could have moved a reminder.
*
@@ -482,7 +482,7 @@ class BoardViewModel(
*/
private fun mutate(
closeEditor: Boolean = false,
block: (ThoughtSync) -> Note?,
block: (Inkwell) -> Note?,
) {
viewModelScope.launch {
state = state.copy(saving = true)
@@ -547,7 +547,7 @@ class BoardViewModel(
private const val VIEW_TRASH = "trash"
fun factory(
core: ThoughtSync,
core: Inkwell,
onRemindersChanged: () -> Unit,
): ViewModelProvider.Factory =
object : ViewModelProvider.Factory {
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
// The colour a LABEL wears when nobody picked one for it.
//
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
/**
* Everything the editor can ask for, as one type.
@@ -1,10 +1,10 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.compose.runtime.saveable.Saver
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import com.fabledsword.thoughtsync.core.checklistItems
import com.fabledsword.thoughtsync.core.checklistRender
import com.fabledsword.inkwell.core.checklistItems
import com.fabledsword.inkwell.core.checklistRender
/**
* One piece of a note body, as the editor DRAWS it.
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import android.text.format.DateUtils
import androidx.compose.foundation.background
@@ -41,8 +41,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.fabledsword.thoughtsync.R
import com.fabledsword.thoughtsync.core.Note
import com.fabledsword.inkwell.R
import com.fabledsword.inkwell.core.Note
/**
* The editor's action bar, along the top of the surface.
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@@ -36,9 +36,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import com.fabledsword.thoughtsync.R
import com.fabledsword.thoughtsync.core.Label
import com.fabledsword.thoughtsync.core.Note
import com.fabledsword.inkwell.R
import com.fabledsword.inkwell.core.Label
import com.fabledsword.inkwell.core.Note
import java.time.DayOfWeek
import java.time.Instant
import java.time.LocalDate
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -16,7 +16,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.fabledsword.thoughtsync.R
import com.fabledsword.inkwell.R
// Shared by the board and the editor.
//
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Column
@@ -12,8 +12,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.fabledsword.thoughtsync.core.LinkPreview
import com.fabledsword.thoughtsync.core.Note
import com.fabledsword.inkwell.core.LinkPreview
import com.fabledsword.inkwell.core.Note
private val PREVIEW_RADIUS = 8.dp
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -38,12 +38,12 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.fabledsword.thoughtsync.R
import com.fabledsword.thoughtsync.core.BodyItem
import com.fabledsword.thoughtsync.core.Note
import com.fabledsword.thoughtsync.core.NoteLabel
import com.fabledsword.thoughtsync.core.bodyTags
import com.fabledsword.thoughtsync.core.checklistItems
import com.fabledsword.inkwell.R
import com.fabledsword.inkwell.core.BodyItem
import com.fabledsword.inkwell.core.Note
import com.fabledsword.inkwell.core.NoteLabel
import com.fabledsword.inkwell.core.bodyTags
import com.fabledsword.inkwell.core.checklistItems
@Composable
fun NoteCard(
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.isSystemInDarkTheme
@@ -25,8 +25,8 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.fabledsword.thoughtsync.core.Label
import com.fabledsword.thoughtsync.core.Note
import com.fabledsword.inkwell.core.Label
import com.fabledsword.inkwell.core.Note
import kotlinx.coroutines.delay
/**
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.annotation.StringRes
import androidx.compose.foundation.background
@@ -20,7 +20,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.fabledsword.thoughtsync.R
import com.fabledsword.inkwell.R
/**
* A bordered block, tinted from the same table the notes use.
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.fillMaxWidth
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import android.app.AlarmManager
import android.content.Context
@@ -17,8 +17,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.app.NotificationManagerCompat
import com.fabledsword.thoughtsync.R
import com.fabledsword.thoughtsync.Reminders
import com.fabledsword.inkwell.R
import com.fabledsword.inkwell.Reminders
/**
* Says so when a reminder would not actually reach anyone.
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import android.os.Build
import androidx.compose.foundation.layout.Arrangement
@@ -27,9 +27,9 @@ import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import com.fabledsword.thoughtsync.R
import com.fabledsword.thoughtsync.core.Compatibility
import com.fabledsword.thoughtsync.core.RevokeOutcome
import com.fabledsword.inkwell.R
import com.fabledsword.inkwell.core.Compatibility
import com.fabledsword.inkwell.core.RevokeOutcome
// Becoming linked: the probe-then-sign-in flow, and the notices around it.
//
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -36,15 +36,15 @@ import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.fabledsword.thoughtsync.R
import com.fabledsword.thoughtsync.UpdateOutcome
import com.fabledsword.thoughtsync.installedVersionName
import com.fabledsword.inkwell.R
import com.fabledsword.inkwell.UpdateOutcome
import com.fabledsword.inkwell.installedVersionName
/**
* Opt-in server pairing.
*
* The whole screen is written around one idea: **being unlinked is not a
* problem.** ThoughtSync is local-first and completely usable having never opened
* problem.** Inkwell is local-first and completely usable having never opened
* this screen, so the unlinked state leads with "Working offline on this device"
* and explains what connecting would ADD, rather than presenting an empty form as
* unfinished setup.
@@ -1,11 +1,11 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import com.fabledsword.thoughtsync.R
import com.fabledsword.thoughtsync.core.Compatibility
import com.fabledsword.thoughtsync.core.SyncOutcome
import com.fabledsword.inkwell.R
import com.fabledsword.inkwell.core.Compatibility
import com.fabledsword.inkwell.core.SyncOutcome
// Turning sync results into sentences.
//
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -6,12 +6,12 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.fabledsword.thoughtsync.core.Compatibility
import com.fabledsword.thoughtsync.core.ProbeResult
import com.fabledsword.thoughtsync.core.RevokeOutcome
import com.fabledsword.thoughtsync.core.SyncOutcome
import com.fabledsword.thoughtsync.core.SyncStatus
import com.fabledsword.thoughtsync.core.ThoughtSync
import com.fabledsword.inkwell.core.Compatibility
import com.fabledsword.inkwell.core.Inkwell
import com.fabledsword.inkwell.core.ProbeResult
import com.fabledsword.inkwell.core.RevokeOutcome
import com.fabledsword.inkwell.core.SyncOutcome
import com.fabledsword.inkwell.core.SyncStatus
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -113,7 +113,7 @@ data class SyncState(
* stored cursor next time (Scribe #2736).
*/
class SyncViewModel(
private val core: ThoughtSync,
private val core: Inkwell,
/**
* Called after a sync that changed the store.
*
@@ -316,7 +316,7 @@ class SyncViewModel(
companion object {
fun factory(
core: ThoughtSync,
core: Inkwell,
onStoreChanged: () -> Unit,
): ViewModelProvider.Factory =
object : ViewModelProvider.Factory {
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -43,8 +43,8 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import com.fabledsword.thoughtsync.R
import com.fabledsword.thoughtsync.core.Label
import com.fabledsword.inkwell.R
import com.fabledsword.inkwell.core.Label
/** The swatch shown beside a tag, and tapped to change its colour. */
private val SWATCH = 22.dp
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -6,8 +6,8 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.fabledsword.thoughtsync.core.Label
import com.fabledsword.thoughtsync.core.ThoughtSync
import com.fabledsword.inkwell.core.Inkwell
import com.fabledsword.inkwell.core.Label
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -57,7 +57,7 @@ data class TagsState(
* the core marks the rows dirty and the next sync carries them.
*/
class TagsViewModel(
private val core: ThoughtSync,
private val core: Inkwell,
/**
* Called after any write that landed.
*
@@ -140,7 +140,7 @@ class TagsViewModel(
* `busy` is cleared in the same assignment that stores the result, so no path
* out of here can leave the screen stuck with its controls disabled.
*/
private fun write(block: (ThoughtSync) -> Unit) {
private fun write(block: (Inkwell) -> Unit) {
if (state.busy) return
state = state.copy(busy = true, error = null)
viewModelScope.launch {
@@ -164,7 +164,7 @@ class TagsViewModel(
companion object {
fun factory(
core: ThoughtSync,
core: Inkwell,
onStoreChanged: () -> Unit,
): ViewModelProvider.Factory =
object : ViewModelProvider.Factory {
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
@@ -59,7 +59,7 @@ private val DarkColors =
)
/**
* Material 3 in ThoughtSync's own colours, following the system light/dark setting.
* Material 3 in Inkwell's own colours, following the system light/dark setting.
*
* DELIBERATELY NOT Material You dynamic colour, which this used until the operator
* saw the first build. Dynamic colour is the more Android-native choice and it
@@ -72,7 +72,7 @@ private val DarkColors =
* If dynamic colour is ever wanted it belongs behind a setting, not as the default.
*/
@Composable
fun ThoughtSyncTheme(
fun InkwellTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit,
) {
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import java.time.LocalDateTime
import java.time.OffsetDateTime
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -25,9 +25,9 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.fabledsword.thoughtsync.AppUpdate
import com.fabledsword.thoughtsync.R
import com.fabledsword.thoughtsync.UpdateOutcome
import com.fabledsword.inkwell.AppUpdate
import com.fabledsword.inkwell.R
import com.fabledsword.inkwell.UpdateOutcome
/**
* Updating the app from the server it is linked to.
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import android.content.Context
import androidx.compose.runtime.getValue
@@ -7,10 +7,10 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.fabledsword.thoughtsync.AppUpdate
import com.fabledsword.thoughtsync.UpdateOutcome
import com.fabledsword.thoughtsync.core.ClientUpdate
import com.fabledsword.thoughtsync.core.ThoughtSync
import com.fabledsword.inkwell.AppUpdate
import com.fabledsword.inkwell.UpdateOutcome
import com.fabledsword.inkwell.core.ClientUpdate
import com.fabledsword.inkwell.core.Inkwell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -61,7 +61,7 @@ data class UpdateState(
* secret this app holds across two languages for no gain.
*/
class UpdateViewModel(
private val core: ThoughtSync,
private val core: Inkwell,
/**
* MUST be the application context — it outlives this view model, and holding an
* Activity here is the textbook way to leak a window.
@@ -207,7 +207,7 @@ class UpdateViewModel(
companion object {
fun factory(
core: ThoughtSync,
core: Inkwell,
context: Context,
): ViewModelProvider.Factory =
object : ViewModelProvider.Factory {
+1 -1
View File
@@ -2,6 +2,6 @@
<resources>
<!-- The product's brand colour, same value the web app's manifest and
<meta name="theme-color"> already use. One source of truth for "what
colour is ThoughtSync" across the three surfaces. -->
colour is Inkwell" across the three surfaces. -->
<color name="ic_launcher_background">#F5C518</color>
</resources>
+1 -1
View File
@@ -6,5 +6,5 @@
truth in XML — the same reason the desktop reads its live theme rather
than hardcoding a window colour.
-->
<style name="Theme.ThoughtSync" parent="android:Theme.Material.NoActionBar" />
<style name="Theme.Inkwell" parent="android:Theme.Material.NoActionBar" />
</resources>
@@ -1,4 +1,4 @@
package com.fabledsword.thoughtsync.ui
package com.fabledsword.inkwell.ui
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
+1 -1
View File
@@ -74,7 +74,7 @@ exceptions:
# right and still applies.
excludes:
- "**/ui/**"
- "**/ThoughtSyncApplication.kt"
- "**/InkwellApplication.kt"
- "**/SyncWorker.kt"
- "**/ReminderReceiver.kt"
- "**/AppUpdate.kt"
+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));
+2 -2
View File
@@ -1,5 +1,5 @@
# Where the generated Kotlin lands. Matches the app's package so the bindings are
# `com.fabledsword.thoughtsync.core.*` rather than something the app has to alias.
# `com.fabledsword.inkwell.core.*` rather than something the app has to alias.
[bindings.kotlin]
package_name = "com.fabledsword.thoughtsync.core"
package_name = "com.fabledsword.inkwell.core"
cdylib_name = "inkwell_ffi"
+1 -1
View File
@@ -18,7 +18,7 @@ dependencyResolutionManagement {
mavenCentral()
}
}
rootProject.name = "ThoughtSync"
rootProject.name = "Inkwell"
// `ffi/` sits beside `app/` but is deliberately NOT a Gradle module: it is a Rust
// crate belonging to the Cargo workspace at the repo root. Gradle reaches it by
+8 -7
View File
@@ -1,4 +1,4 @@
# CI Requirements — ThoughtSync
# CI Requirements — Inkwell
> Spec lives in [`docs/process.md`](https://git.fabledsword.com/bvandeusen/CI-runner/src/branch/main/docs/process.md)
> in the CI-Runner repo.
@@ -39,7 +39,7 @@ entirely on `ci-python:3.14`.
install` cold cost is a non-blocker.
- Build gates on `typecheck` + `lint` only. The `test` job runs in parallel for
visibility but does not block the dev image push. DB-backed / integration tests
run against the dev image manually — ThoughtSync's unit tests are DB-free (no
run against the dev image manually — Inkwell's unit tests are DB-free (no
Postgres service lane in CI yet).
- `dev` push -> `:dev` + `:<sha>`; `v*` tag -> `:latest` + `:<version>` + `:<sha>`
(family rule 46).
@@ -87,7 +87,7 @@ parts. Three of them are family rules for a reason:
`docker ps` by it. A spaced or underscored name breaks the filter.
- **Service hostnames are not routable** on this runner (rule 79), so the step resolves
the Postgres container's bridge IP with `docker ps --filter` + `docker inspect` and
builds `THOUGHTSYNC_DATABASE_URL` from it. `postgres:5432` will not connect.
builds `INKWELL_DATABASE_URL` from it. `postgres:5432` will not connect.
- **`run:` is busybox sh** (rule 81) — no `/dev/tcp` — so the readiness wait is a small
Python heredoc. Its terminator must dedent to column 0 after YAML strips the block
indent; check with `yaml.safe_load` and print the `run` string if you edit it.
@@ -205,7 +205,7 @@ backend/frontend push.
## Android lane — being rebuilt (M12)
The Tauri-mobile Android lane is gone. Android is a native Kotlin/Compose client
over the shared `thoughtsync-core` crate instead — see Scribe note 2730 for the
over the shared `inkwell-core` crate instead — see Scribe note 2730 for the
decision and milestone M12 for the arc.
The image it will run on already exists: **`ci-rust-android:1.97`**, repurposed
@@ -218,7 +218,7 @@ second image, and JDK 25 (which requires **Gradle 9.1+** in this repo's wrapper
the old JDK 17 pin existed only because Tauri generated a Gradle 8.x project).
The Rust pin is in LOCKSTEP with `ci-tauri` and `ci-tauri-win`. All three build
`thoughtsync-core` from one workspace `Cargo.lock` under `--locked`, so a
`inkwell-core` from one workspace `Cargo.lock` under `--locked`, so a
mismatched Rust minor across the lanes would mean divergent resolution for no
reason. Bump the three together or not at all.
@@ -333,8 +333,9 @@ differs from CI is worse than none.
**This reproduces CI exactly, not approximately.** On the 2026-08-18 run the
local test binary hashes (`thoughtsync_core-bbaae79723888ad1`,
`thoughtsync_desktop_lib-9d162263f8d0aca3`, `thoughtsync_ffi-fc557b96dc795e27`)
matched CI run 3931's byte for byte. Same image, same lockfile, same units.
`thoughtsync_desktop_lib-9d162263f8d0aca3`, `thoughtsync_ffi-fc557b96dc795e27`,
named for the crates as they were before the rename to Inkwell) matched CI run
3931's byte for byte. Same image, same lockfile, same units.
`target/` persists on the host between runs, so after the first cold build these
take seconds (~30s for clippy). It is gitignored and reaches ~1.4 GB; delete it
+2 -2
View File
@@ -1,9 +1,9 @@
# ThoughtSync desktop (Tauri v2)
# Inkwell desktop (Tauri v2)
Local-first desktop client. The window loads the shared **Vue 3 frontend** from
`../frontend`; the Rust core (`src-tauri`) owns the on-device store and the opt-in
sync engine (built out across the M10 milestone). Works fully offline; optionally
syncs to a self-hosted ThoughtSync server.
syncs to a self-hosted Inkwell server.
## Layout
+15 -14
View File
@@ -1,6 +1,6 @@
# ThoughtSync desktop — Arch package
# Inkwell desktop — Arch package
A **prebuilt** native pacman package, published as an asset on every ThoughtSync
A **prebuilt** native pacman package, published as an asset on every Inkwell
release. Nothing to compile, no toolchain to install.
Installing natively on Arch matters for more than tidiness: pacman pulls
@@ -15,22 +15,22 @@ Easiest — the one-command installer picks this package automatically on any
pacman system:
```sh
curl -fsSL https://git.fabledsword.com/bvandeusen/thoughtsync/raw/branch/dev/desktop/packaging/install.sh | sh
curl -fsSL https://git.fabledsword.com/bvandeusen/inkwell/raw/branch/dev/desktop/packaging/install.sh | sh
```
That installs the newest build from `main`. To follow the rolling development
channel instead, pass the flag through the pipe:
```sh
curl -fsSL https://git.fabledsword.com/bvandeusen/thoughtsync/raw/branch/dev/desktop/packaging/install.sh | sh -s -- --channel dev
curl -fsSL https://git.fabledsword.com/bvandeusen/inkwell/raw/branch/dev/desktop/packaging/install.sh | sh -s -- --channel dev
```
Or grab the `.pkg.tar.*` from the
[releases page](https://git.fabledsword.com/bvandeusen/thoughtsync/releases)
[releases page](https://git.fabledsword.com/bvandeusen/inkwell/releases)
and install it directly:
```sh
sudo pacman -U thoughtsync-*-x86_64.pkg.tar.*
sudo pacman -U inkwell-*-x86_64.pkg.tar.*
```
The compression suffix depends on what the build image provides — `.zst` when
@@ -39,17 +39,18 @@ all of them; only the filename differs.
Either way you get:
- `/usr/bin/thoughtsync` — the app
- `/usr/share/applications/thoughtsync.desktop` — the menu entry
- `/usr/share/icons/hicolor/*/apps/thoughtsync.png` — themed icons
- `/usr/bin/inkwell` — the app
- `/usr/share/applications/inkwell.desktop` — the menu entry
- `/usr/share/icons/hicolor/*/apps/inkwell.png` — themed icons
Launch **ThoughtSync** from your app menu, or run `thoughtsync`.
Launch **Inkwell** from your app menu, or run `inkwell`.
Uninstall: `sudo pacman -R thoughtsync`.
Uninstall: `sudo pacman -R inkwell`.
The package was called `thoughtsync-desktop` before; it declares `replaces`/
`conflicts` on that name, so an upgrade from it is a normal `pacman -U` and
leaves nothing behind.
The package was called `thoughtsync` until the app was renamed Inkwell, and
`thoughtsync-desktop` before that. It declares `replaces`/`conflicts` on both
names, so an upgrade from either is a normal `pacman -U` and leaves nothing
behind.
## How the package is built
+3 -3
View File
@@ -72,8 +72,8 @@ ASSETS=(
"$BUNDLE_ROOT"/arch/*.pkg.tar.*
"$WIN_BUNDLE_ROOT"/nsis/*.exe
"$WIN_BUNDLE_ROOT"/nsis/*.exe.sig
"$REPO_ROOT"/android/dist/thoughtsync.apk
"$REPO_ROOT"/android/dist/thoughtsync-android.json
"$REPO_ROOT"/android/dist/inkwell.apk
"$REPO_ROOT"/android/dist/inkwell-android.json
)
# nullglob drops PATTERNS that match nothing — it does nothing for a path with no
# wildcard in it, which stays in the array as a literal and reaches curl as a file
@@ -149,7 +149,7 @@ fi
echo "==> Creating release for $TAG"
BODY=$(cat <<JSON
{"tag_name":"$TAG","name":"Inkwell $CHANNEL_LABEL","draft":false,"prerelease":$RELEASE_PRERELEASE,
"body":"Inkwell $CHANNEL_LABEL.\n\n**Desktop**\n\n- **Debian / Ubuntu** — native \`.deb\`\n- **Arch / CachyOS** — native \`.pkg.tar.*\`\n- **everything else** — \`.AppImage\` (de-bundled graphics: renders on any GPU/Wayland setup)\n\nInstall / update — picks the right one for your system:\n\`\`\`\ncurl -fsSL $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/raw/branch/dev/desktop/packaging/install.sh | $INSTALL_TAIL\n\`\`\`\n\n**Android** — \`thoughtsync.apk\`. Copy it and \`thoughtsync-android.json\` into your server's \`/var/inkwell/client/\` and the server will offer it to your devices; see docs/android-distribution.md.$CHANNEL_NOTE"}
"body":"Inkwell $CHANNEL_LABEL.\n\n**Desktop**\n\n- **Debian / Ubuntu** — native \`.deb\`\n- **Arch / CachyOS** — native \`.pkg.tar.*\`\n- **everything else** — \`.AppImage\` (de-bundled graphics: renders on any GPU/Wayland setup)\n\nInstall / update — picks the right one for your system:\n\`\`\`\ncurl -fsSL $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/raw/branch/dev/desktop/packaging/install.sh | $INSTALL_TAIL\n\`\`\`\n\n**Android** — \`inkwell.apk\`. Copy it and \`inkwell-android.json\` into your server's \`/var/inkwell/client/\` and the server will offer it to your devices; see docs/android-distribution.md.$CHANNEL_NOTE"}
JSON
)
+1 -1
View File
@@ -229,7 +229,7 @@ if [ "${PRUNE_OLD_ASSETS:-false}" = "true" ]; then
# every desktop push regardless. That is exactly what happened on run
# 4098, which swept the APK run 4092 had just published.
case "$asset_name" in
latest.json|inkwell-desktop.json|thoughtsync.apk|thoughtsync-android.json) continue ;;
latest.json|inkwell-desktop.json|inkwell.apk|inkwell-android.json) continue ;;
*"$APP_VERSION"*) continue ;;
esac
echo " removing $asset_name"
+10 -10
View File
@@ -1,6 +1,6 @@
# Getting the Android app onto your server
ThoughtSync's server hands out the Android client it syncs with. Once an APK is in
Inkwell's server hands out the Android client it syncs with. Once an APK is in
place, anyone with an account on that server can download it from **Account →
Linked devices**, and linked phones can update themselves from it.
@@ -34,7 +34,7 @@ pinning.
## Overriding it
If you want a specific build — testing something, or holding back — drop it in
`/var/thoughtsync/client/` and it wins over the image's copy.
`/var/inkwell/client/` and it wins over the image's copy.
That directory is shared with the desktop clients the server hands out, and
**precedence is decided per platform**: dropping in an APK overrides the baked APK
@@ -44,14 +44,14 @@ Two files, both required:
| File | What it is |
| --- | --- |
| `thoughtsync.apk` | the client |
| `thoughtsync-android.json` | `{version_name, version_code, size, sha256}` |
| `inkwell.apk` | the client |
| `inkwell-android.json` | `{version_name, version_code, size, sha256}` |
The sidecar exists because an APK keeps its version in a binary manifest that
needs the Android build tools to read. CI writes it beside the APK, where the
real values are already known.
`/var/thoughtsync` is the same volume that holds attachments (`Config.DATA_DIR`),
`/var/inkwell` is the same volume that holds attachments (`Config.DATA_DIR`),
so a build dropped there survives container recreation — and survives an image
upgrade, which is the point of an override.
@@ -59,10 +59,10 @@ Both files are published to the rolling dev-channel release (tag `dev-rolling`)
on every green Android build:
```sh
REPO=https://git.fabledsword.com/bvandeusen/thoughtsync
REPO=https://git.fabledsword.com/bvandeusen/inkwell
TOKEN=... # a Fabled-Git token with read access; the instance is private
for f in thoughtsync.apk thoughtsync-android.json; do
for f in inkwell.apk inkwell-android.json; do
curl -fsSL -H "Authorization: token $TOKEN" \
-o "/tmp/$f" "$REPO/releases/download/dev-rolling/$f"
done
@@ -70,8 +70,8 @@ done
# Copy the sidecar LAST. A sidecar that does not match the APK beside it is not a
# client, so a half-finished copy falls back to the image's build rather than
# advertising a lie.
docker compose cp /tmp/thoughtsync.apk app:/var/thoughtsync/client/
docker compose cp /tmp/thoughtsync-android.json app:/var/thoughtsync/client/
docker compose cp /tmp/inkwell.apk app:/var/inkwell/client/
docker compose cp /tmp/inkwell-android.json app:/var/inkwell/client/
```
To go back to whatever the image ships, delete both files.
@@ -88,7 +88,7 @@ offering a button that fails.
## What happens if you get it wrong
All of these describe an override in `/var/thoughtsync/client/`. A broken
All of these describe an override in `/var/inkwell/client/`. A broken
override does not take the feature away — it falls through to the build the image
shipped with, which is the whole reason precedence runs in that direction.
+7 -7
View File
@@ -1,6 +1,6 @@
# Putting ThoughtSync on the public internet
# Putting Inkwell on the public internet
ThoughtSync is built to run on a LAN and works fine there with no ceremony. Exposing
Inkwell is built to run on a LAN and works fine there with no ceremony. Exposing
it changes the threat model: anyone can now reach the login form, and any account is
one guessed password away from someone's whole note history.
@@ -65,7 +65,7 @@ Which fix depends on where your proxy runs:
the `ports:` block from `docker-compose.yml`. The proxy reaches the app over the
compose network; no published port is needed at all, and this is the safest of the
two because there is no host port to reach even from the host.
- **Proxy on the host**: set `THOUGHTSYNC_BIND=127.0.0.1` in `.env`, so the port
- **Proxy on the host**: set `INKWELL_BIND=127.0.0.1` in `.env`, so the port
exists but only the host itself can use it.
To check which you have: `docker compose ps` shows the published ports, and
@@ -73,12 +73,12 @@ To check which you have: `docker compose ps` shows the published ports, and
app is still answering around the proxy. It should not be.
**5. Have a backup that includes the files.** Attachments are files on the
`thoughtsync-data` volume, not rows — a `pg_dump` restores notes whose images are all
`inkwell-data` volume, not rows — a `pg_dump` restores notes whose images are all
gone. Back up both:
```
docker compose exec -T db pg_dump -U thoughtsync thoughtsync > notes.sql
docker run --rm -v thoughtsync-data:/d -v "$PWD":/out alpine tar czf /out/media.tgz -C /d .
docker compose exec -T db pg_dump -U inkwell inkwell > notes.sql
docker run --rm -v inkwell-data:/d -v "$PWD":/out alpine tar czf /out/media.tgz -C /d .
```
## What the app already does
@@ -132,7 +132,7 @@ know. They are the reason not to hand out open registration to strangers.
The app allows plain HTTP so a self-hosted server on a LAN is usable at all — Android
blocks cleartext by default from API 28, and `http://192.168.1.10:8000` is exactly the
case ThoughtSync is built for. Over the public internet, link the phone to the
case Inkwell is built for. Over the public internet, link the phone to the
**HTTPS** hostname. The sync screen shows a warning before any credential field
whenever the address it probed was `http://`; on a public network that warning means
what it says.
+3 -3
View File
@@ -1,4 +1,4 @@
# ThoughtSync sync protocol
# Inkwell sync protocol
The contract the local-first native clients (Tauri desktop, Android) implement
against. The server is the **sync hub**: each client keeps a full local store
@@ -38,8 +38,8 @@ token, or even has an account:
```
The client identifies itself on every request with
`X-ThoughtSync-Client: thoughtsync-desktop/<app version>` and
`X-ThoughtSync-Protocol: <n>`.
`X-Inkwell-Client: inkwell-desktop/<app version>` and
`X-Inkwell-Protocol: <n>`.
### `sync_features` — why versions alone aren't enough
+2 -2
View File
@@ -1,11 +1,11 @@
{
"name": "thoughtsync-frontend",
"name": "inkwell-frontend",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "thoughtsync-frontend",
"name": "inkwell-frontend",
"version": "0.1.0",
"dependencies": {
"pinia": "^2.2.0",
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "thoughtsync-frontend",
"name": "inkwell-frontend",
"version": "0.1.0",
"private": true,
"type": "module",
+1 -1
View File
@@ -6,7 +6,7 @@
// and avoid deploy staleness, this SW does NOT cache the app shell, hashed build
// assets, or any /api response — everything but the offline fallback goes
// straight to the network.
const CACHE = "thoughtsync-shell-v1";
const CACHE = "inkwell-shell-v1";
const OFFLINE_URL = "/offline.html";
self.addEventListener("install", (event) => {
+2 -2
View File
@@ -88,8 +88,8 @@ echo "==> Collecting the $channel clients"
# Its sidecar is published whole by the Android lane — an APK keeps its version in
# a binary AXML manifest, so the values are recorded where they were already known.
# Copied verbatim rather than rebuilt here.
if fetch "$BASE/thoughtsync.apk" "$dest/inkwell.apk" &&
fetch "$BASE/thoughtsync-android.json" "$dest/inkwell-android.json"; then
if fetch "$BASE/inkwell.apk" "$dest/inkwell.apk" &&
fetch "$BASE/inkwell-android.json" "$dest/inkwell-android.json"; then
echo " android $(field "$dest/inkwell-android.json" version_name)"
else
# Both or neither. Half a pair is worse than none: the server would read a
+2 -2
View File
@@ -110,7 +110,7 @@ published_for() {
| sed -E 's/.*"([^"]+)"$/\1/'; } || true
;;
android)
{ fetch "$SERVER/$REPO/releases/download/$_tag/thoughtsync-android.json" \
{ fetch "$SERVER/$REPO/releases/download/$_tag/inkwell-android.json" \
| grep -oE '"version_code"[[:space:]]*:[[:space:]]*[0-9]+' | head -1 \
| grep -oE '[0-9]+$'; } || true
;;
@@ -126,7 +126,7 @@ published_name() {
case "$1" in
desktop) published_for desktop "$2" ;;
android)
{ fetch "$SERVER/$REPO/releases/download/$_tag/thoughtsync-android.json" \
{ fetch "$SERVER/$REPO/releases/download/$_tag/inkwell-android.json" \
| grep -oE '"version_name"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1 \
| sed -E 's/.*"([^"]+)"$/\1/'; } || true
;;