rename: the server is Inkwell — package, env vars, image, compose, export marker
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Android / Build, or is the channel already serving this? (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 15s
CI & Build / integration (push) Successful in 45s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 4m17s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 7m33s
Desktop (Tauri) / Update manifest (push) Successful in 7s
Android / Kotlin + Rust (APK) (push) Successful in 11m25s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Android / Build, or is the channel already serving this? (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 15s
CI & Build / integration (push) Successful in 45s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 4m17s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 7m33s
Desktop (Tauri) / Update manifest (push) Successful in 7s
Android / Kotlin + Rust (APK) (push) Successful in 11m25s
Step 2 of milestone 481. The operator chose a full rename (Scribe note 5071), so this goes past the display strings into the identities: - src/thoughtsync → src/inkwell; every import, the Dockerfile and both compose commands, alembic env, pyproject - THOUGHTSYNC_* → INKWELL_* (database URL, secret key, log level, tag/port/bind) - container data dir /var/thoughtsync → /var/inkwell - image git.fabledsword.com/bvandeusen/inkwell; Postgres user/db default inkwell; CI's integration service follows - the files the image serves are inkwell.*. fetch-clients.sh still fetches the thoughtsync-named release assets, because the lanes that publish them are renamed in steps 3 and 4 - exports are written with app "inkwell" Two deliberate exceptions, both because data rides on them: - compose volumes are now named explicitly and overridable (INKWELL_DB_VOLUME, INKWELL_DATA_VOLUME), so a deployment installed as ThoughtSync points at the volumes and DB identity it already has. .env.example says exactly what to set - import still accepts app "thoughtsync", because exports written before the rename are backups. Tested both ways Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+2
-2
@@ -1,12 +1,12 @@
|
||||
import pytest
|
||||
|
||||
from thoughtsync.config import Config
|
||||
from inkwell.config import Config
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _isolated_data_dir(tmp_path, monkeypatch):
|
||||
"""Keep any media writes on an isolated temp dir rather than the fixed
|
||||
/var/thoughtsync. DB-free unit tests never actually hit it (create_app takes its
|
||||
/var/inkwell. DB-free unit tests never actually hit it (create_app takes its
|
||||
signing key from env-or-random, not a file), but this stays defensive."""
|
||||
monkeypatch.setattr(Config, "DATA_DIR", str(tmp_path / "data"))
|
||||
yield
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import uuid
|
||||
|
||||
from thoughtsync.acl import visible_to_user
|
||||
from thoughtsync.models.user import User
|
||||
from inkwell.acl import visible_to_user
|
||||
from inkwell.models.user import User
|
||||
|
||||
|
||||
def test_visible_to_user_builds_owner_or_shared_predicate():
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from thoughtsync.app import create_app
|
||||
from inkwell.app import create_app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from thoughtsync.app import create_app
|
||||
from inkwell.app import create_app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -61,7 +61,7 @@ async def test_no_version_in_the_environment_reports_unknown(monkeypatch):
|
||||
happens to hold, so bumping the packaging version cannot make this pass for the
|
||||
wrong reason.
|
||||
"""
|
||||
from thoughtsync import __version__
|
||||
from inkwell import __version__
|
||||
|
||||
monkeypatch.delenv("APP_VERSION", raising=False)
|
||||
reported = await reported_version()
|
||||
|
||||
+14
-10
@@ -3,9 +3,9 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from thoughtsync import client_dist
|
||||
from thoughtsync.app import create_app
|
||||
from thoughtsync.client_dist import (
|
||||
from inkwell import client_dist
|
||||
from inkwell.app import create_app
|
||||
from inkwell.client_dist import (
|
||||
APK_NAME,
|
||||
BY_ID,
|
||||
MANIFEST_NAME,
|
||||
@@ -15,7 +15,7 @@ from thoughtsync.client_dist import (
|
||||
release,
|
||||
releases,
|
||||
)
|
||||
from thoughtsync.config import Config
|
||||
from inkwell.config import Config
|
||||
|
||||
# DB-free, like the rest of this suite — the test lane runs no Postgres. That is
|
||||
# why the advertisement is asserted through `advertisement()` rather than through
|
||||
@@ -61,7 +61,7 @@ def coded(platform_id: str, value: int):
|
||||
def _empty_baked_client(tmp_path, monkeypatch):
|
||||
"""Point the baked-in copy at an empty directory.
|
||||
|
||||
In a source checkout `src/thoughtsync/client/` does not exist, so these tests
|
||||
In a source checkout `src/inkwell/client/` does not exist, so these tests
|
||||
would pass anyway — but only by accident of where they are run. A built image
|
||||
has real clients there, and a test that silently depends on which tree it is in
|
||||
is one that will eventually lie.
|
||||
@@ -118,13 +118,17 @@ def test_every_platform_has_its_own_filenames():
|
||||
assert not set(artifacts) & set(sidecars)
|
||||
|
||||
|
||||
def test_the_android_names_are_the_ones_already_published():
|
||||
"""Pinned because renaming them is a tidy-up that strands every installed phone.
|
||||
def test_the_android_names_are_the_ones_the_image_build_writes():
|
||||
"""Pinned against `packaging/fetch-clients.sh`, which writes the APK and its
|
||||
sidecar under these names when it bakes them into the image. The two are a pair
|
||||
with nothing checking them against each other, so a rename on one side reads as
|
||||
"no Android client" on the other rather than as an error.
|
||||
|
||||
The Android lane publishes these exact names and CI bakes them in under them.
|
||||
Phones never ask for these names; they poll `/api/client/android`. The names
|
||||
changed once, with the rename to Inkwell (Scribe note 5071).
|
||||
"""
|
||||
assert APK_NAME == "thoughtsync.apk"
|
||||
assert MANIFEST_NAME == "thoughtsync-android.json"
|
||||
assert APK_NAME == "inkwell.apk"
|
||||
assert MANIFEST_NAME == "inkwell-android.json"
|
||||
|
||||
|
||||
# --- absence is an ordinary answer -------------------------------------------
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from thoughtsync.app import create_app
|
||||
from inkwell.app import create_app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
+17
-17
@@ -22,20 +22,20 @@ import pytest
|
||||
import pytest_asyncio
|
||||
from sqlalchemy import select, text
|
||||
|
||||
from thoughtsync import ratelimit
|
||||
from thoughtsync.app import create_app
|
||||
from thoughtsync.db import dispose_engine, session_scope
|
||||
from thoughtsync.models.label import NoteLabel
|
||||
from thoughtsync.models.note import Note
|
||||
from thoughtsync.models.user import User
|
||||
from thoughtsync.notes.tags import _lift_and_reconcile_tags
|
||||
from thoughtsync.settings import get_setting, live, refresh_live, reset_live, set_settings
|
||||
from thoughtsync.notes.checklist import parse_items, set_item_checked
|
||||
from thoughtsync.notes.helpers import derive_display_title
|
||||
from thoughtsync.models.note_link_preview import NoteLinkPreview
|
||||
from thoughtsync.models.note_revision import NoteRevision
|
||||
from thoughtsync.revisions import REVISION_WINDOW_MINUTES, should_snapshot
|
||||
from thoughtsync.unfurl_queue import _unfurl_new_urls, detect_urls
|
||||
from inkwell import ratelimit
|
||||
from inkwell.app import create_app
|
||||
from inkwell.db import dispose_engine, session_scope
|
||||
from inkwell.models.label import NoteLabel
|
||||
from inkwell.models.note import Note
|
||||
from inkwell.models.user import User
|
||||
from inkwell.notes.tags import _lift_and_reconcile_tags
|
||||
from inkwell.settings import get_setting, live, refresh_live, reset_live, set_settings
|
||||
from inkwell.notes.checklist import parse_items, set_item_checked
|
||||
from inkwell.notes.helpers import derive_display_title
|
||||
from inkwell.models.note_link_preview import NoteLinkPreview
|
||||
from inkwell.models.note_revision import NoteRevision
|
||||
from inkwell.revisions import REVISION_WINDOW_MINUTES, should_snapshot
|
||||
from inkwell.unfurl_queue import _unfurl_new_urls, detect_urls
|
||||
|
||||
pytestmark = pytest.mark.integration
|
||||
|
||||
@@ -305,7 +305,7 @@ async def test_auto_unfurl_stores_a_preview_and_skips_what_is_cached(db, owner,
|
||||
calls.append(url)
|
||||
return {"url": url, "title": f"T {url}", "description": None, "image_url": None, "site_name": "example.com"}
|
||||
|
||||
monkeypatch.setattr("thoughtsync.unfurl_queue.unfurl", fake_unfurl)
|
||||
monkeypatch.setattr("inkwell.unfurl_queue.unfurl", fake_unfurl)
|
||||
|
||||
await _unfurl_new_urls(note.id, note.body)
|
||||
assert sorted(calls) == ["https://example.com/a", "https://example.com/b"]
|
||||
@@ -331,7 +331,7 @@ async def test_auto_unfurl_drops_a_preview_whose_url_left_the_body(db, owner, mo
|
||||
# Simulate the body changing while the request was in the air.
|
||||
return {"url": url, "title": "T", "description": None, "image_url": None, "site_name": None}
|
||||
|
||||
monkeypatch.setattr("thoughtsync.unfurl_queue.unfurl", fake_unfurl)
|
||||
monkeypatch.setattr("inkwell.unfurl_queue.unfurl", fake_unfurl)
|
||||
note.body = "changed my mind"
|
||||
await db.commit()
|
||||
|
||||
@@ -352,7 +352,7 @@ async def test_detection_agrees_with_what_gets_stored(db, owner, monkeypatch):
|
||||
async def fake_unfurl(url):
|
||||
return {"url": url, "title": "T", "description": None, "image_url": None, "site_name": None}
|
||||
|
||||
monkeypatch.setattr("thoughtsync.unfurl_queue.unfurl", fake_unfurl)
|
||||
monkeypatch.setattr("inkwell.unfurl_queue.unfurl", fake_unfurl)
|
||||
await _unfurl_new_urls(note.id, body)
|
||||
|
||||
stored = {
|
||||
|
||||
@@ -2,9 +2,9 @@ import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
from thoughtsync.app import create_app
|
||||
from thoughtsync.models.label import Label
|
||||
from thoughtsync.serialize import serialize_label
|
||||
from inkwell.app import create_app
|
||||
from inkwell.models.label import Label
|
||||
from inkwell.serialize import serialize_label
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
+35
-8
@@ -1,12 +1,15 @@
|
||||
import io
|
||||
import json
|
||||
import zipfile
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
|
||||
from thoughtsync.app import create_app
|
||||
from thoughtsync.common import coerce_bool, parse_dt
|
||||
from thoughtsync.colors import NOTE_COLORS
|
||||
from thoughtsync.models.note import Note
|
||||
from thoughtsync.notes.checklist import (
|
||||
from inkwell.app import create_app
|
||||
from inkwell.common import coerce_bool, parse_dt
|
||||
from inkwell.colors import NOTE_COLORS
|
||||
from inkwell.models.note import Note
|
||||
from inkwell.notes.checklist import (
|
||||
append_item,
|
||||
parse_items,
|
||||
remove_item,
|
||||
@@ -14,13 +17,15 @@ from thoughtsync.notes.checklist import (
|
||||
set_item_text,
|
||||
strip_marker,
|
||||
)
|
||||
from thoughtsync.unfurl_queue import detect_urls
|
||||
from thoughtsync.notes import (
|
||||
from inkwell.unfurl_queue import detect_urls
|
||||
from inkwell.notes import (
|
||||
_ImportBudget,
|
||||
_attachment_ext,
|
||||
_header_filename,
|
||||
_keep_spec,
|
||||
_native_spec,
|
||||
_note_markdown,
|
||||
_read_import_specs,
|
||||
_safe_filename,
|
||||
_slugify,
|
||||
_usec_to_dt,
|
||||
@@ -32,7 +37,8 @@ from thoughtsync.notes import (
|
||||
parse_list_items,
|
||||
parse_tags,
|
||||
)
|
||||
from thoughtsync.notes.tags import split_body_tags
|
||||
from inkwell.notes.import_export import NATIVE_APP_MARKERS
|
||||
from inkwell.notes.tags import split_body_tags
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -672,3 +678,24 @@ def test_native_spec_of_a_pre_m304_export_still_carries_its_items():
|
||||
# items at all.
|
||||
old = {"body": "shopping", "items": [{"text": "milk", "checked": True}]}
|
||||
assert _native_spec(old)["items"] == [{"text": "milk", "checked": True}]
|
||||
|
||||
|
||||
def _export_zip(app_marker: str) -> zipfile.ZipFile:
|
||||
buf = io.BytesIO()
|
||||
with zipfile.ZipFile(buf, "w") as zf:
|
||||
zf.writestr("notes.json", json.dumps({"app": app_marker, "notes": [{"body": "kept"}]}))
|
||||
return zipfile.ZipFile(io.BytesIO(buf.getvalue()))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("marker", ["inkwell", "thoughtsync"])
|
||||
def test_an_export_imports_under_either_name(marker):
|
||||
# "thoughtsync" is what every export written before the rename to Inkwell says.
|
||||
# Those zips are backups, so they have to keep restoring as native exports rather
|
||||
# than falling through to the Keep importer, which would find nothing in them.
|
||||
specs, source = _read_import_specs(_export_zip(marker), _ImportBudget())
|
||||
assert source == "inkwell"
|
||||
assert [s["body"] for s in specs] == ["kept"]
|
||||
|
||||
|
||||
def test_new_exports_are_written_as_inkwell():
|
||||
assert NATIVE_APP_MARKERS[0] == "inkwell"
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ The whole security property is "a caller cannot forge their own address", and it
|
||||
on counting in from the RIGHT of the header rather than the left. These are the cases
|
||||
that tell the two apart — pure functions, no request context, no database.
|
||||
"""
|
||||
from thoughtsync.proxy import forwarded_for, trusted_entry
|
||||
from thoughtsync.settings import live
|
||||
from inkwell.proxy import forwarded_for, trusted_entry
|
||||
from inkwell.settings import live
|
||||
|
||||
PEER = "10.0.0.1" # the socket address: our own proxy, or the caller when unproxied
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ import time
|
||||
|
||||
import pytest
|
||||
|
||||
from thoughtsync import ratelimit
|
||||
from thoughtsync.settings import live
|
||||
from thoughtsync.app import create_app
|
||||
from thoughtsync.ratelimit import SlidingWindow
|
||||
from inkwell import ratelimit
|
||||
from inkwell.settings import live
|
||||
from inkwell.app import create_app
|
||||
from inkwell.ratelimit import SlidingWindow
|
||||
|
||||
|
||||
def window(limit: int, window_s: float) -> SlidingWindow:
|
||||
|
||||
@@ -2,14 +2,14 @@ from datetime import datetime, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
|
||||
from thoughtsync.retention import (
|
||||
from inkwell.retention import (
|
||||
SWEEP_BATCH,
|
||||
SWEEP_INTERVAL_SECONDS,
|
||||
SWEEP_STARTUP_DELAY_SECONDS,
|
||||
expired_before,
|
||||
sweep_expired_trash,
|
||||
)
|
||||
from thoughtsync.settings import REGISTRY, get_public_config, validate_updates
|
||||
from inkwell.settings import REGISTRY, get_public_config, validate_updates
|
||||
|
||||
NOW = datetime(2026, 7, 26, 12, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from thoughtsync.app import create_app
|
||||
from thoughtsync.saved_filters import clean_params
|
||||
from inkwell.app import create_app
|
||||
from inkwell.saved_filters import clean_params
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from thoughtsync.security import generate_token, hash_password, hash_token, verify_password
|
||||
from inkwell.security import generate_token, hash_password, hash_token, verify_password
|
||||
|
||||
|
||||
def test_password_roundtrip():
|
||||
|
||||
@@ -6,7 +6,7 @@ hook, and this suite has no Postgres.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
from thoughtsync.app import create_app
|
||||
from inkwell.app import create_app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from thoughtsync.settings import REGISTRY, validate_updates
|
||||
from inkwell.settings import REGISTRY, validate_updates
|
||||
|
||||
|
||||
def test_registry_has_expected_keys():
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@ from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
|
||||
from thoughtsync.app import create_app
|
||||
from thoughtsync.sync import (
|
||||
from inkwell.app import create_app
|
||||
from inkwell.sync import (
|
||||
DEFAULT_LIMIT,
|
||||
MAX_LIMIT,
|
||||
MIN_CLIENT_PROTOCOL_VERSION,
|
||||
|
||||
@@ -2,8 +2,8 @@ import ipaddress
|
||||
|
||||
import pytest
|
||||
|
||||
from thoughtsync.app import create_app
|
||||
from thoughtsync.unfurl import UnfurlError, extract_preview, is_public_ip, validate_url
|
||||
from inkwell.app import create_app
|
||||
from inkwell.unfurl import UnfurlError, extract_preview, is_public_ip, validate_url
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
Reference in New Issue
Block a user