Commit graph

560 commits

Author SHA1 Message Date
Adam Brown
c1fac8ff1e Prepared for release: v3.8.0 2026-08-04 21:47:41 -07:00
Adam Brown
32d8b6be4a Bump gradle 2026-08-04 20:53:39 -07:00
Adam Brown
7f998f2618 ktor bump
Some checks are pending
Build CI / build (push) Waiting to run
Build CI / static-analysis (push) Waiting to run
Build CI / android-instrumented-tests (push) Waiting to run
Build CI / iOS compile & test (push) Waiting to run
Build CI / iOS UI tests (push) Waiting to run
PublishInternal / publish-google-play (push) Waiting to run
2026-08-04 08:40:48 -07:00
Adam Brown
ce54ad7054 Update AGP
Some checks are pending
Build CI / build (push) Waiting to run
Build CI / static-analysis (push) Waiting to run
Build CI / android-instrumented-tests (push) Waiting to run
Build CI / iOS compile & test (push) Waiting to run
Build CI / iOS UI tests (push) Waiting to run
PublishInternal / publish-google-play (push) Waiting to run
2026-08-04 00:32:23 -07:00
Adam Brown
e2906293bc Upgrade Rich Text Editor 2026-08-04 00:30:42 -07:00
Adam Brown
cfac12ed5f Lib bumps
Remove kotlinx.datetime
2026-08-04 00:27:59 -07:00
Adam Brown
eadb412805
Update org.jetbrains:markdown to 0.7.8 (#846)
Patch release: nested code fences parse correctly, reference link
labels must immediately follow the link text, and username@host is
recognized as part of a URL.
2026-08-03 22:58:46 -07:00
Adam Brown
fdeab5b992 Prepared for release: v3.7.2 2026-07-27 13:45:42 -07:00
Adam Brown
3375d81b55 Prepared for release: v3.7.1 2026-07-23 14:36:28 -07:00
Adam Brown
8742202de5 Prepared for release: v3.7.0 2026-07-21 09:10:11 -07:00
Adam Brown
7ab8d656a7
Bump material-kolor to 5.0.0 (#764)
* update testcontainers

* Update material-kolor to 5.0.0

* Regenerate desktop aboutlibraries metadata
2026-07-21 09:04:04 -07:00
Adam Brown
fab1da62de Revert "Prepared for release: v3.7.0"
This reverts commit 9bfc9ec8e1.
2026-07-21 09:01:45 -07:00
Adam Brown
9bfc9ec8e1 Prepared for release: v3.7.0 2026-07-21 00:56:45 -07:00
Adam Brown
b876bb8d05 update testcontainers 2026-07-21 00:27:16 -07:00
Adam Brown
61ee72c426 lib updates 2026-07-21 00:03:46 -07:00
Adam Brown
a337b058a4
Add a bytecode guard for responds in value position (#755)
Scans compiled route handlers for the Unit coercion the compiler emits
when a respond is the value of an exhaustive when, which is the shape
that broke the bio endpoint and is invisible in source review.

Includes a positive control: a fixture written in the unsafe shape that
the detector must keep flagging, so a codegen change fails the test
instead of silently disabling it. Also asserts a floor on the number of
handlers scanned so a broken filter can't pass vacuously.
2026-07-19 20:31:26 -07:00
renovate[bot]
c8bd8fb25f
Update dependency ch.qos.logback:logback-classic to v1.5.38 (#735)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Adam Brown <adamwbrown@gmail.com>
2026-07-17 16:25:28 -07:00
Adam Brown
1fe142725b Migrate :common DI to Koin compiler plugin DSL with compile-time safety
Adopt the Koin Kotlin compiler plugin (io.insert-koin.compiler.plugin 1.0.2)
and convert :common's DI from classic DSL to the plugin DSL
(org.koin.plugin.module.dsl), enabling compile-time DI graph validation
(compileSafety = true) while keeping the centralized module{} layout.

- Convert singleOf/factoryOf/scopedOf(::X) -> single<X>()/factory<X>()/scoped<X>()
  across mainModule (root + ProjectDefScope block), migratorModule, and the
  android/desktop/ios platformModule + exampleProjectModule actuals.
- Provider functions and when/config bodies use create(::fn) to keep auto-wiring
  and validation; explicit-lambda and named() bindings stay classic.
- Add koin-annotations (BOM-managed) for @Provided; mark SandboxFileAccess
  @Provided (its definition lives in the desktop app module, external to :common).
- Relocate the iOS startKoin into a plugin-less bootstrapKoin() helper in :base so
  common's iOS compilation is no longer a full-graph (A3) aggregator.

Green with compileSafety=true on desktop, android, common metadata, and
desktopTest (1404 tests). iOS/Native is blocked by an upstream plugin bug: the
generated dsl_single hint functions encode named() qualifiers as parameter names,
which K/N's IdSignature ignores, so same-type qualified definitions (the three
CoroutineContext dispatchers) clash during klib serialization. Documented inline.
2026-07-14 23:37:54 -07:00
Adam Brown
c5c42f2938 Prepared for release: v3.6.1 2026-07-12 23:30:39 -07:00
Adam Brown
501de2e597 Prepared for release: v3.6.0 2026-07-07 23:22:53 -07:00
renovate[bot]
14cd4c5f4f Update dependency org.postgresql:postgresql to v42.7.13 (#727)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-07 23:11:29 -07:00
renovate[bot]
374d598c98
Update dependency org.jetbrains:markdown to v0.7.6 (#726)
* Projects are now detected only if they have a project.toml file, other directories are ignored

* Update plugin ee.schimke.composeai.preview to v0.16.24 (#724)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update dependency org.jetbrains:markdown to v0.7.6

---------

Co-authored-by: Adam Brown <adamwbrown@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-07 21:41:42 -07:00
renovate[bot]
2499b7efdc
Update dependency org.jetbrains.kotlinx:kotlinx-collections-immutable to v0.5.1 (#717)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-02 22:26:55 -07:00
Adam Brown
177e4bbe4f Prepared for release: v3.5.3 2026-06-30 15:16:45 -07:00
Adam Brown
43a836844d
Add iOS UI smoke tests (XCUITest) (#707)
* Add iOS UI smoke tests (XCUITest)

Adds an iOS UI smoke-test suite that drives the real app on a simulator via
XCUITest, the iOS analogue of the android/src/androidTest Compose UI tests.
The whole iOS UI is Compose Multiplatform, and CMP (1.8+) maps Compose testTags
to iOS accessibilityIdentifiers automatically, so the tests target the same
testTags the Android suite uses.

Workflows covered (all green on simulator):
- LaunchSmoke: app boots through the SwiftUI entry point, Koin + data migration
  run, project selection renders.
- ProjectWorkflow: create + open a project (exercises the create dialog text
  entry and navigation into the editor).
- SceneEditorWorkflow: create a scene and confirm it opens in the editor.
- NotesWorkflow: navigate to Notes and open the create-note screen.

Setup:
- ios/scripts/add_ui_test_target.rb idempotently creates the iosUITests
  UI-testing target + shared scheme via the xcodeproj gem (the folder previously
  had source files but no actual target).
- ios/scripts/disable_sim_hardware_keyboard.sh forces the software keyboard so
  XCUITest text entry lands (Compose fields need it).
- ios-ui-tests CI job runs the suite on a simulator and uploads the xcresult on
  failure.
- composeUi ProjectCreateDialog: tag the name field so the create flow is
  targetable (also benefits Android).

Known limitation: the app's custom rich-text editors (scene body, note body via
MarkdownEditField) do not report keyboard focus to XCUITest, so their text entry
can't be driven (only standard Compose text fields can). The scene and notes
tests therefore stop at "editor opens" / "creation screen opens"; the full
edit/create-with-body paths remain covered by the Android suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* iOS UI tests: type into note body via composetexteditor 2.3.0

composetexteditor 2.3.0 publishes text-editing accessibility semantics on its
editor, so XCUITest can now drive text entry into it. Bump the dependency and
promote the Notes UI test to the full Android-parity flow: create a note by
typing into the body, then assert its card appears.

The scene edit+save flow stays scoped to "scene opens" for now — text entry
works, but the scene editor's initial-buffer gating + dirty-driven save make
the save affordance unreliable to assert from an IME-driven edit; promoting it
is a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 10:39:04 -07:00
Adam Brown
57de1c6522 Prepared for release: v3.5.2 2026-06-29 22:15:55 -07:00
renovate[bot]
f4a53b4e98
Update dependency org.postgresql:postgresql to v42.7.12 (#702)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-29 21:35:46 -07:00
renovate[bot]
f51f9d739d
Update aboutlibraries to v15.0.3 (#699)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-29 21:18:01 -07:00
Adam Brown
10a2cd7668
Merge pull request #689 from Wavesonics/renovate/junit-framework-monorepo
Update junit-framework monorepo to v6.1.1
2026-06-28 22:20:34 -07:00
renovate[bot]
2c834c1ed0
Update dependency io.github.pdvrieze.xmlutil:core to v1.0.0 2026-06-28 23:02:28 +00:00
renovate[bot]
2894178571
Update junit-framework monorepo to v6.1.1 2026-06-28 14:58:07 +00:00
Adam Brown
46ce5042d3 Prepared for release: v3.5.1 2026-06-27 23:51:29 -07:00
Adam Brown
1a0a50bd76 Prepared for release: v3.5.0 2026-06-27 00:39:39 -07:00
Adam Brown
87a242d171
Encrypt iOS auth tokens at rest via Keychain; run iOS tests in CI (#680)
Implements the Keychain-backed AuthTokenStore for iOS, completing the
client-side token-at-rest encryption left as a TODO when Android and
desktop were done. The account-keyed token map is stored as a single
kSecClassGenericPassword item (encrypted by the OS) instead of the
plaintext file store. Backed by multiplatform-settings' KeychainSettings.

Matches the other stores' post-refactor shape: no plaintext-file
migration (that FileAuthTokenStore was never shipped); the genuine legacy
case - inline tokens in server.json - is handled by migrateInlineTokens
in the common ServerSettingsFilesystemDatasource.

Also unblocks and enables the iOS unit test suite in CI. iOS test
linking failed because okio-fakefilesystem references the deprecated
kotlinx.datetime.Clock typealias, which double-binds during Kotlin/Native
klib caching. Since commonTest declared okio-fakefilesystem, it polluted
the Native test classpath even though only JVM tests use FakeFileSystem.
Moved that dependency to jvmTest (still reaches desktopTest). The
ios-compile job now also runs :common:iosSimulatorArm64Test (110+ tests
across 11 suites that previously never ran).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 23:19:43 -07:00
renovate[bot]
6e89a16c2a
Update dependency io.ktor.plugin to v3.5.1 (#673)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-26 11:07:11 -07:00
renovate[bot]
4017f1d0cf
Update dependency ch.qos.logback:logback-classic to v1.5.37 (#676)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-26 11:06:59 -07:00
Adam Brown
4eea2a3cba
Fix editor undo/redo broken by decoration spans (#675)
Bump composetexteditor to 2.2.5, which keeps decoration spans
(spell-check underlines, find highlights) out of the undo history and
off the editOperations stream via the new RichSpanStyle.isDecoration
flag. This fixes undo/redo appearing dead in the scene/focus editors.

With decorations no longer emitting, the local isDecorationOnly() edit-
stream filter is redundant — remove it and its two collectors' filters.
Route DraftCompare diff highlights through updateRichSpans (the batch
overlay API), since they are overlays too.

* Stop spell-check spans being wiped on undo

toEditorSpellChecker() built a new EditorSpellChecker instance every
recomposition. rememberSpellCheckState keys its full-rescan LaunchedEffect
on that instance, so it restarted runFullSpellCheck on every edit —
removing all spell spans and re-adding them across suspend points. A
recomposition mid-rescan (now reachable since undo actually mutates text)
cancelled it after the remove-all, wiping every span document-wide with
no recovery.

Remember the EditorSpellChecker per dictionary so the rescan effect only
runs when the dictionary actually changes.
2026-06-26 10:02:26 -07:00
renovate[bot]
562a628ee7
Update ktor monorepo to v3.5.1 (#666)
* Update ktor monorepo to v3.5.1
* split versions
2026-06-25 22:39:16 -07:00
renovate[bot]
04a6ddabaa
Update dependency ch.qos.logback:logback-classic to v1.5.36 (#671)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-25 21:33:56 -07:00
renovate[bot]
fa9d237b27
Update aboutlibraries to v15.0.2 (#664)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-25 20:54:39 -07:00
Adam Brown
9fa0dbc9dd
Add RTF as a story export format (#667)
Mirrors the recently added RTF import with an RTF export option. RTF joins
Markdown/EPUB/PDF/DOCX in the export dialog and the export pipeline.

- Add ExportFormat.Rtf with the .rtf extension and application/rtf mime type
- New RtfStoryRenderer renders the title page, a linked Contents page, and
  per-chapter sections, converting each chapter's markdown into formatted RTF
  runs (bold/italic, headings, lists, quotes, code, links, horizontal rules).
  Heading and link colors pick up the project theme accents, matching DOCX/PDF.
- Wire the format into ExportStoryUseCase, the export options dialog, and the
  Android save-picker mime type; localized format label added for all locales
- Tests cover well-formed/balanced RTF output, metadata, bookmarks, escaping,
  unicode escapes, theme colors, an end-to-end use-case run, and a round-trip
  back through the RTF importer
2026-06-25 19:23:16 -07:00
Adam Brown
092652edfa Add RTF import for new projects
Adds RTF as an import format alongside Markdown. RTF has no headings, so
the importer collects each paragraph with its formatting and splits into
scenes by one of three user-chosen strategies: formatting (outline level
/ font size / bold), a chapter-heading regex, or a single scene.

Shared scene/group folding is extracted into ImportStructure so both
importers reuse it, and a StoryImporterRegistry routes by file extension.
Importers now take ByteArray so RTF bytes aren't decoded prematurely.

Markdown gains an Auto detection mode (the new default) that reads a lone
leading heading as the project title and picks the chapter level by
frequency. The create-dialog help affordance becomes an icon button.
2026-06-25 00:15:37 -07:00
Adam Brown
810ae23c0d Encrypt auth tokens at rest per platform (F-4)
Replace the plaintext FileAuthTokenStore binding with platform-specific
encrypted stores behind the same AuthTokenStore interface, wired via a new
expect/actual authTokenStoreModule.

Android: EncryptedSharedPrefsAuthTokenStore backed by EncryptedSharedPreferences
with a Keystore-backed AES256_GCM master key (androidx.security:security-crypto).

Desktop: EncryptedFileAuthTokenStore writes the token-map JSON as AES/GCM/NoPadding
to the config directory. The key is derived (PBKDF2WithHmacSHA256) from the OS user
name and home dir plus a static salt, with no key file on disk, so a copied token
file is useless on another machine or user. A random 12-byte IV is prepended per
write and owner-only POSIX perms are applied best-effort. Decryption failure is
treated as no tokens rather than crashing. This guards against casual disk
scraping and off-machine copies, not same-user local malware that can re-derive
the key.

iOS: still uses the plaintext file store pending a Keychain-backed implementation
(TODO marker in the iOS binding).

Migration: a legacy plaintext auth_tokens.json from an intermediate build is
imported into the encrypted store and deleted on first access; existing encrypted
tokens win on key collision so a stale plaintext entry cannot clobber a fresh
session.
2026-06-24 16:07:40 -07:00
Adam Brown
c5ee60feee Encode dynamic project-name path segments to stop URL segment injection
Every project-scoped client API built its request path by raw string
interpolation of projectName, e.g. "/api/project/$userId/$projectName/begin_sync".
That string reached the shared url() builder whose only path handling was
pathSegments = path.split("/"). Because the split ran on the already-interpolated
string, a projectName containing "/" was split into extra discrete path segments
and a ".." survived as a literal traversal dot-segment, so the outbound request
could target a different endpoint than the {userId}/{projectName}/{action}
template intended (e.g. a malicious sync server returning a project named
"p/../../../api/account/test_auth").

The shared ProjectNameValidator permits "/", "\" and "." (they are encoded to
disk-safe lookalikes only when used as a directory name), so a server-supplied
project name persists verbatim and then injects into every subsequent
project-scoped request under the same host with the bearer token attached.

Fix: each dynamic value is now percent-encoded into a single opaque path
segment via String.encodeUrlPathSegment() before interpolation, and the sink
sets encodedPath directly. Embedded "/" becomes %2F so it cannot create extra
segments, and an all-dots segment is encoded to %2E so a ".." name cannot act as
a traversal segment. The validator is intentionally left unchanged: tightening
it to reject "/" or "." would break syncing for already-valid existing project
names, so encoding is the backward-compatible fix and the on-disk
encodeForFilename behavior is untouched.

Adds a MockEngine test asserting a malicious projectName collapses to a single
encoded segment in the outbound URL across ProjectDataApi, ServerProjectApi and
WritingActivityApi.
2026-06-24 14:45:27 -07:00
renovate[bot]
9ad37ee315
Update logback monorepo (#637)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-23 16:35:51 -07:00
Adam Brown
3b23dc6cf0 Prepared for release: v3.4.2 2026-06-23 10:10:05 -07:00
Adam Brown
77f1eb1884 Prepared for release: v3.4.1 2026-06-23 00:45:34 -07:00
Adam Brown
080c403619 Prepared for release: v3.4.0 2026-06-22 22:02:40 -07:00
renovate[bot]
fe1f8162f9
Update aboutlibraries to v15 (#633)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-22 14:15:24 -07:00
renovate[bot]
68ffcc1eb1
Update kotlinx.collections.immutable to v0.5.0 (#630)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-22 11:43:14 -07:00