Commit graph

3562 commits

Author SHA1 Message Date
Adam Brown
076f3477f4 New translations messages_en.properties (Turkish)
[ci skip]
2026-10-02 22:29:07 -07:00
Adam Brown
0b371c6f29 New translations messages_en.properties (Portuguese, Brazilian)
[ci skip]
2026-10-02 22:29:05 -07:00
Adam Brown
4124655953 New translations messages_en.properties (Ukrainian)
[ci skip]
2026-10-02 22:29:03 -07:00
Adam Brown
c08653ecac New translations messages_en.properties (Italian)
[ci skip]
2026-10-02 22:29:01 -07:00
Adam Brown
e5cb2c0992 New translations messages_en.properties (German)
[ci skip]
2026-10-02 22:29:00 -07:00
Adam Brown
0191c81510 New translations messages_en.properties (Spanish)
[ci skip]
2026-10-02 22:28:58 -07:00
Adam Brown
66125ec1c0 New translations messages_en.properties (French)
[ci skip]
2026-10-02 22:28:56 -07:00
Adam Brown
5c58fb3239 Update source file Messages_en.properties
[ci skip]
2026-10-02 22:28:51 -07:00
Adam Brown
4edff36e95
New Crowdin updates (#968)
* New translations messages_en.properties (Turkish)

[ci skip]

* New translations strings.xml (Turkish)

[ci skip]

* New translations strings_about_app.xml (Turkish)

[ci skip]

* New translations strings_account_settings.xml (Turkish)

[ci skip]

* New translations strings_desktop.xml (Turkish)

[ci skip]

* New translations strings_drafts.xml (Turkish)

[ci skip]

* New translations strings_encyclopedia.xml (Turkish)

[ci skip]

* New translations strings_notes.xml (Turkish)

[ci skip]

* New translations strings_project_home.xml (Turkish)

[ci skip]

* New translations strings_project_navigation.xml (Turkish)

[ci skip]

* New translations strings_project_select_navigation.xml (Turkish)

[ci skip]

* New translations strings_projects_list.xml (Turkish)

[ci skip]

* New translations strings_scene_editor.xml (Turkish)

[ci skip]

* New translations strings_scene_list.xml (Turkish)

[ci skip]

* New translations strings_sync.xml (Turkish)

[ci skip]

* New translations strings_timeline.xml (Turkish)

[ci skip]

* New translations strings_android.xml (Turkish)

[ci skip]

* New translations full_description.txt (Turkish)

[ci skip]

* New translations short_description.txt (Turkish)

[ci skip]

* New translations title.txt (Turkish)

[ci skip]

* New translations strings_globalsearch.xml (Turkish)

[ci skip]

* New translations strings_ideas.xml (Turkish)

[ci skip]

* New translations strings_wear_pairing.xml (Turkish)

[ci skip]
2026-09-29 23:24:46 -07:00
Adam Brown
569b6c0297
New Crowdin updates (#965)
* New translations strings_about_app.xml (Italian)

[ci skip]

* New translations messages_en.properties (Italian)

[ci skip]

* New translations strings_wear_pairing.xml (Italian)

[ci skip]

* New translations full_description.txt (Italian)

[ci skip]

* New translations strings_project_home.xml (Italian)

[ci skip]

* New translations strings_project_navigation.xml (Italian)

[ci skip]

* New translations strings_android.xml (Italian)

[ci skip]
2026-09-27 21:04:01 -07:00
Adam Brown
1cdcdad657
Recover from an undecryptable auth token keyset instead of crashing (#960)
Some checks failed
Build CI / build (push) Has been cancelled
Build CI / static-analysis (push) Has been cancelled
Build CI / android-instrumented-tests (push) Has been cancelled
Build CI / iOS compile & test (push) Has been cancelled
Build CI / iOS UI tests (push) Has been cancelled
PublishInternal / publish-google-play (push) Has been cancelled
* Recover from an undecryptable auth token keyset instead of crashing

Exclude the encrypted token prefs from backups, and attach the R8 mappings
to GitHub releases.

* Reset the Keystore master key along with the token prefs
2026-09-27 17:43:25 -07:00
Adam Brown
13b078f26c
Add core-splashscreen to the Android phone app (#964) 2026-09-27 17:41:58 -07:00
Adam Brown
a39b1e7fd4
Update ComposeTextEditor to 2.8.0 for Compose 1.12 IME APIs (#961)
2.6.0 was built against Compose 1.11 and lacks the IME members 1.12 made
abstract, so focusing an editor on iOS threw IrLinkageError on
ImeComposeStateAdapter.text and killed the app.
2026-09-27 16:22:06 -07:00
Wavesonics
3f038d836f
Fix keyboard options on non-prose text fields
Some checks failed
Build CI / build (push) Has been cancelled
Build CI / static-analysis (push) Has been cancelled
Build CI / android-instrumented-tests (push) Has been cancelled
Build CI / iOS compile & test (push) Has been cancelled
Build CI / iOS UI tests (push) Has been cancelled
PublishInternal / publish-google-play (push) Has been cancelled
URL, numeric, tag, word-list, and delete-confirmation fields inherited
sentence capitalization and autocorrect, which inserted a space after
'.' in server URLs.
2026-09-25 23:52:26 -07:00
Adam Brown
1a433d78f9
New Crowdin updates (#955)
* Update source file full_description.txt

[ci skip]

* New translations full_description.txt (French)

[ci skip]

* New translations full_description.txt (Spanish)

[ci skip]

* New translations full_description.txt (German)

[ci skip]

* New translations full_description.txt (Italian)

[ci skip]

* New translations full_description.txt (Ukrainian)

[ci skip]

* New translations full_description.txt (Portuguese, Brazilian)

[ci skip]

* New translations full_description.txt (French)

[ci skip]

* New translations full_description.txt (Spanish)

[ci skip]

* New translations full_description.txt (German)

[ci skip]

* New translations full_description.txt (Italian)

[ci skip]

* New translations full_description.txt (Ukrainian)

[ci skip]

* New translations full_description.txt (Portuguese, Brazilian)

[ci skip]

* Update source file full_description.txt

[ci skip]

* New translations messages_en.properties (French)

[ci skip]

* New translations messages_en.properties (Spanish)

[ci skip]

* New translations messages_en.properties (German)

[ci skip]

* New translations messages_en.properties (Italian)

[ci skip]

* New translations messages_en.properties (Ukrainian)

[ci skip]

* New translations messages_en.properties (Portuguese, Brazilian)

[ci skip]

* Update source file Messages_en.properties

[ci skip]
2026-09-25 23:40:09 -07:00
Wavesonics
04de488fb0
Treat any-script letters, marks, and digits as word characters in reference matching
Names no longer match inside words containing accented or non-Latin letters ("Ana" in "Anaïs", "Иван" in "Иванов").
2026-09-25 17:51:38 -07:00
Adam Brown
1c22fdb926 Prepared for release: v3.11.0
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-09-24 23:47:29 -07:00
Adam Brown
5be23d0d0a Merge dependency updates and window size class replacement 2026-09-24 23:39:00 -07:00
Adam Brown
9ccb3d6216 Replace the Material 3 window size class dependency with our own
The desktop implementation of calculateWindowSizeClass reads an AWT window,
which does not exist under the Tao backend, and its LocalWindow reference was
removed in Compose 1.12. Size classes now derive from layout constraints.
2026-09-24 23:36:44 -07:00
Adam Brown
675fcafe17 Update Compose, Ktor, Glance, pdfkmp, kmp-zip and WorkManager 2026-09-24 23:33:27 -07:00
Adam Brown
47a2ac7c27 Update colorpicker-compose to 1.3.0 2026-09-24 22:19:44 -07:00
Adam Brown
cb4f7d0875 Update Coil to 3.6.3 2026-09-24 22:19:12 -07:00
Adam Brown
7bddcea632 Update SQLDelight to 2.4.0 2026-09-24 22:18:33 -07:00
Adam Brown
30d75760f1 Update Cairn to 0.4.0 2026-09-24 22:06:43 -07:00
Adam Brown
1a26dee270 Update Nucleus to 2.5.18 2026-09-24 21:56:05 -07:00
Adam Brown
277b1555c1 Update FileKit to 0.16.0 2026-09-24 21:55:24 -07:00
Adam Brown
a501bd791e Update Wear Compose to 1.7.0 2026-09-24 21:54:44 -07:00
Adam Brown
e3c1b62a5d Update ComposeTextEditor to 2.6.0 2026-09-24 21:50:29 -07:00
Adam Brown
9dcf462178
Keep the Wear pairing capability from resource shrinking (#958) 2026-09-24 21:49:16 -07:00
Adam Brown
e895ed3a8e Bump low-risk dependencies to their latest patch releases 2026-09-24 21:38:29 -07:00
Adam Brown
61618061f6 Keep the Wear pairing capability from resource shrinking 2026-09-24 21:09:26 -07:00
Adam Brown
5001376e78
Add reader kudos to published stories (#957)
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
Signed-in readers can leave kudos at the end of a publicly published
story: up to four craft chips and one reaction. There are no comments
or ratings, so there is nothing to moderate.

- Kudos are anonymous. Authors see per-chip totals on their story page;
  the public page shows a chip's name, never a count, once three readers
  pick it.
- The reader card loads as its own HTMX fragment on the last page, so
  the story page's ETag never depends on kudos. Private shares never
  show it.
- Authors can turn kudos off per story; existing kudos are kept.
- New story_kudos and story_kudos_opt_out tables (migration 9.sqm).
  Chip keys are a code enum, rows are purged in both directions on
  account deletion, and soft-deleted givers drop out of the counts.
- Story URL resolution is shared between the story page, the read
  beacon, and the kudos routes.
2026-09-23 23:57:36 -07:00
Wavesonics
fcbfa1bee5
Exclude standalone Markdown markers from word counts
Only count runs of non-whitespace containing a letter or digit, so
horizontal rules, heading, bullet and blockquote markers are not words.
Move countWords to :base and use it on the server too.
2026-09-23 23:55:53 -07:00
Wavesonics
e79859d091
Upload store screenshots and images on production release 2026-09-23 19:08:46 -07:00
Wavesonics
d8835b118c
Enable R8 obfuscation for Android and Wear; upload mapping files to Play 2026-09-23 18:00:47 -07:00
Wavesonics
61b3b21ab9
Prepared for release: v3.10.1
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-09-23 07:53:53 -07:00
Wavesonics
ab759feae1
Tighten Wear list items after review
Headers take the library's own transformation, free text gets a side
inset and sync log entries are capped at five lines so they stay inside
a round bezel. The capture splash holds until the project list loads,
list content modifiers are remembered per item, and one app theme is
shared by the manifest and the splash style.
2026-09-23 07:51:13 -07:00
Wavesonics
e771482c1a
Fix Wear OS Play rejection: splash, listing text, round screens
Branded launch: core-splashscreen theme shows the launcher icon on black
for MainActivity and CaptureActivity.

Play listing names the Capture tile and the complication.

Every TransformingLazyColumn item now uses the Material 3 scroll
transformation so buttons, headers and text shrink and fade at the
bezel instead of being clipped on round displays. The custom
screenContentPadding helper is dropped; the scaffold's padding already
includes the horizontal inset.
2026-09-23 00:34:13 -07:00
Adam Brown
4f047fdd7e
Add Wear OS screenshots and listing text (#954)
Some checks failed
Build CI / build (push) Has been cancelled
Build CI / static-analysis (push) Has been cancelled
Build CI / android-instrumented-tests (push) Has been cancelled
Build CI / iOS compile & test (push) Has been cancelled
Build CI / iOS UI tests (push) Has been cancelled
PublishInternal / publish-google-play (push) Has been cancelled
* Rename Microsoft Store display name to Hammer: Story Editor

* Add Wear OS screenshots and a Wear OS section to the Play listing
2026-09-20 21:21:01 -07:00
Adam Brown
d1dc121639
Split release store notes into Google Play and Apple tabs (#953) 2026-09-20 21:04:29 -07:00
Adam Brown
6394005f60 Prepared for release: v3.10.0
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-09-20 10:16:01 -07:00
Adam Brown
48fd14000c
New Crowdin updates (#926)
* New translations strings_about_app.xml (French)

[ci skip]

* New translations strings_account_settings.xml (French)

[ci skip]

* New translations strings_encyclopedia.xml (French)

[ci skip]

* New translations messages_en.properties (French)

[ci skip]

* New translations messages_en.properties (French)

[ci skip]

* New translations messages_en.properties (Spanish)

[ci skip]

* New translations messages_en.properties (German)

[ci skip]

* New translations messages_en.properties (Italian)

[ci skip]

* New translations messages_en.properties (Ukrainian)

[ci skip]

* New translations messages_en.properties (Portuguese, Brazilian)

[ci skip]

* Update source file Messages_en.properties

[ci skip]

* New translations strings_sync.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_encyclopedia.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_sync.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_about_app.xml (French)

[ci skip]

* New translations strings_account_settings.xml (French)

[ci skip]

* New translations strings_about_app.xml (Spanish)

[ci skip]

* New translations strings_account_settings.xml (Spanish)

[ci skip]

* New translations strings_about_app.xml (German)

[ci skip]

* New translations strings_account_settings.xml (German)

[ci skip]

* New translations strings_about_app.xml (Italian)

[ci skip]

* New translations strings_account_settings.xml (Italian)

[ci skip]

* New translations strings_about_app.xml (Ukrainian)

[ci skip]

* New translations strings_account_settings.xml (Ukrainian)

[ci skip]

* New translations strings_about_app.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_account_settings.xml (Portuguese, Brazilian)

[ci skip]

* Update source file strings_about_app.xml

[ci skip]

* Update source file strings_account_settings.xml

[ci skip]

* New translations strings_about_app.xml (French)

[ci skip]

* New translations strings_about_app.xml (Spanish)

[ci skip]

* New translations strings_about_app.xml (German)

[ci skip]

* New translations strings_about_app.xml (Italian)

[ci skip]

* New translations strings_about_app.xml (Ukrainian)

[ci skip]

* New translations strings_about_app.xml (Portuguese, Brazilian)

[ci skip]

* Update source file strings_about_app.xml

[ci skip]

* New translations strings_account_settings.xml (French)

[ci skip]

* New translations strings_account_settings.xml (Spanish)

[ci skip]

* New translations strings_account_settings.xml (German)

[ci skip]

* New translations strings_account_settings.xml (Italian)

[ci skip]

* New translations strings_account_settings.xml (Ukrainian)

[ci skip]

* New translations strings_account_settings.xml (Portuguese, Brazilian)

[ci skip]

* New translations messages_en.properties (French)

[ci skip]

* New translations strings_sync.xml (French)

[ci skip]

* New translations strings_wear_pairing.xml (French)

[ci skip]

* New translations messages_en.properties (Spanish)

[ci skip]

* New translations strings_sync.xml (Spanish)

[ci skip]

* New translations strings_wear_pairing.xml (Spanish)

[ci skip]

* New translations messages_en.properties (German)

[ci skip]

* New translations strings_sync.xml (German)

[ci skip]

* New translations strings_wear_pairing.xml (German)

[ci skip]

* New translations messages_en.properties (Italian)

[ci skip]

* New translations strings_sync.xml (Italian)

[ci skip]

* New translations strings_wear_pairing.xml (Italian)

[ci skip]

* New translations messages_en.properties (Ukrainian)

[ci skip]

* New translations strings_sync.xml (Ukrainian)

[ci skip]

* New translations strings_wear_pairing.xml (Ukrainian)

[ci skip]

* New translations messages_en.properties (Portuguese, Brazilian)

[ci skip]

* New translations strings_sync.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_wear_pairing.xml (Portuguese, Brazilian)

[ci skip]

* Update source file strings_account_settings.xml

[ci skip]

* Update source file strings_sync.xml

[ci skip]

* Update source file strings_wear_pairing.xml

[ci skip]

* Update source file Messages_en.properties

[ci skip]

* New translations strings_sync.xml (French)

[ci skip]

* New translations strings_sync.xml (Spanish)

[ci skip]

* New translations strings_sync.xml (German)

[ci skip]

* New translations strings_sync.xml (Italian)

[ci skip]

* New translations strings_sync.xml (Ukrainian)

[ci skip]

* New translations strings_sync.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_encyclopedia.xml (French)

[ci skip]

* New translations strings_scene_editor.xml (French)

[ci skip]

* New translations strings_encyclopedia.xml (Spanish)

[ci skip]

* New translations strings_scene_editor.xml (Spanish)

[ci skip]

* New translations strings_encyclopedia.xml (German)

[ci skip]

* New translations strings_scene_editor.xml (German)

[ci skip]

* New translations strings_encyclopedia.xml (Italian)

[ci skip]

* New translations strings_scene_editor.xml (Italian)

[ci skip]

* New translations strings_encyclopedia.xml (Ukrainian)

[ci skip]

* New translations strings_scene_editor.xml (Ukrainian)

[ci skip]

* New translations strings_encyclopedia.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_scene_editor.xml (Portuguese, Brazilian)

[ci skip]
2026-09-20 10:02:03 -07:00
Adam Brown
cbc88944a4
Add a project-scoped user spelling dictionary (#939)
Add a project-scoped user spelling dictionary (#939)

Writers can whitelist words per project: "Add to dictionary" on a flagged word in
the scene editor and focus mode, plus an add/remove word list in Project Settings
under Spell checking.

Words live in ProjectData.dictionaryWords so they sync with the project. A conflict
confined to the word list merges both sides by union with no resolver; other
conflicting fields still go through the existing resolver with the dictionary
unioned. The union is verbatim, so a word a newer build stored under laxer rules
is never deleted server-side. Sync writes also re-apply any project-data edit made
after the phase snapshot, so an edit landing mid-sync is no longer clobbered.

ProjectDictionaryService now feeds user words to the checker alongside encyclopedia
words, independent of the encyclopedia toggle. HdHairlineTagField and the new
HdHairlineWordListField share an extracted HdHairlineChipInput.
2026-09-20 09:45:19 -07:00
Adam Brown
66875346d0
Widen the character set for encyclopedia and draft names (#952)
Encyclopedia entry titles and scene draft names were still on the old
restricted set while project and scene names had moved to the shared
ProjectNameValidator. Both now use that validator, so punctuation like
`. , ! ? : ( ) & - "` works, and draft names accept non-Latin letters.

Both filename formats move to the `~` delimiter, since `-` is now a legal
name character:

  encyclopedia  type~id~name.toml, images type~id~image.ext
  drafts        sceneId~draftId~name~timestamp.md

Names round-trip through encodeForFilename/decodeFromFilename, so
OS-forbidden characters become lookalikes on disk. Legacy patterns are
kept for reads, and path resolution falls back to the legacy filename,
because unlike scenes these two rebuild the filename from the def rather
than scanning the tree.

Migration2_3 renames existing files and PROJECT_DATA_VERSION goes to 3.
It canonicalises names through the encoder, otherwise a name ending in a
space (which the old rules allowed) would migrate to a file that no
rebuilt path could ever match. Draft names are now trimmed at the save
and sync boundaries the way entry names already were.

A synced name containing a path separator is no longer rejected outright;
it is encoded to a lookalike and stays one path segment, matching how
scene names already behave. Containment rests on the isWithin guard. The
reserved `~` delimiter is still rejected.
2026-09-20 09:01:26 -07:00
Adam Brown
97e5a2d196
Ship the watch app to Google Play's Wear OS tracks (#951)
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
The Android lanes build and upload the wear bundle after the phone's, and prepareForRelease writes release notes under the wear version code too.
2026-09-19 20:27:02 -07:00
Adam Brown
6738235141
Wear OS app: capture notes and story ideas from the wrist (#950)
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
A standalone watch client that dictates notes into subscribed projects
and story ideas, then syncs them on its own session.

- New :wear module: capture activity, tile and complication, project
  subscriptions, sync log, and a WorkManager periodic plus
  after-capture sync behind a single SyncCoordinator
- Pairing over the Wearable Data Layer: the phone confirms the request
  and mints a session for the watch's install via the new
  /api/account/pair_install endpoint. Play Services builds only; the
  F-Droid build ships without it
- Manual sign-in on the watch, with an Android 17 local network
  permission check before contacting a LAN server
- Plaintext sync is opt-in: typing an http:// URL selects it, with a
  warning in server setup. HTTPS stays the default, and legacy
  settings always restore as HTTPS
- Account sync extracted into SyncAccountUseCase, shared by the phone
  project list and the watch
- temporaryProjectTask ref-counts concurrent users so one task no
  longer closes a scope another is still using
- Wear version codes are offset from phone codes so both can ship in
  one Play listing
- Crash handler and FileLogger moved into common for reuse
2026-09-19 10:58:19 -07:00
Adam Brown
4189d272c4
Point iOS downloads at the unified App Store listing (#949)
Some checks failed
Build CI / build (push) Has been cancelled
Build CI / static-analysis (push) Has been cancelled
Build CI / android-instrumented-tests (push) Has been cancelled
Build CI / iOS compile & test (push) Has been cancelled
Build CI / iOS UI tests (push) Has been cancelled
PublishInternal / publish-google-play (push) Has been cancelled
The iOS build now ships under the same App Store record as macOS, so the
home page and README link to id6770841038.
* Offer the iOS app with a smart app banner on the home page
2026-09-15 17:06:39 -07:00
Adam Brown
3df7f27617
Say why a login failed instead of answering a bare 401 (#937)
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
* Say why a login failed instead of answering a bare 401

A failed login gave nobody anything to work with. StatusPages matched status 401
and re-responded with a bare status for API calls, discarding the HttpResponseError
the route had just written, and the client's own 401 branch discarded whatever
body did survive in favour of a generic string. Two layers erasing the same
message, so a self-hoster saw "401 Unauthorized" in the log, an empty body on the
wire, and a generic message in the app.

StatusPages now leaves API responses alone, since the routes answer 401 with
their own body, and the client parses the body for every status and only falls
back to a generic message when the server sent nothing usable.

On top of that:

- HttpResponseError carries an optional errorCode from a shared ApiErrorCode
  vocabulary. It is optional and the shared serializer ignores unknown keys, so
  it is compatible with servers and clients on either side of this change.
- Account creation failures use accurate statuses rather than a blanket 409:
  400 for a policy or email violation, 409 for a real conflict, 403 for the
  whitelist. Login answers 403 when the whitelist was the problem, since that is
  not a credential failure.
- The login path logs which failure occurred. The response still cannot
  distinguish an unknown account from a wrong password, because that would let
  anyone enumerate users, but the operator's log now can.

The docs gain the password policy (8 to 64 characters, no complexity rule,
nothing stripped or truncated) and the log lines to look for, which is what the
reporter of #835 asked for.

* Allow the round-trip test accounts now that Allowed Users is always on

Only the first account on a server is exempt, so every later address the test
creates has to be on the list or account creation answers 403.

* Point the login-failure note at the Allowed Users section

The section was renamed, so the anchor was dead.

* Cover the account error statuses and the client's failure body

AccountErrorCodeTest walks the create and refresh failures a client branches on:
existing email, email pending deletion, malformed email, short password, unknown
refresh token. ApiFailureBodyTest covers the other end, where a server message
has to survive instead of being replaced by the generic one.

RecordingStrRes moves to its own file so both API tests can use it.

* Mark the password error codes as non-secrets for semgrep
2026-09-14 19:05:25 -07:00
Adam Brown
404d386d12
Add a Copy Diagnostics action to the About screen (#940)
* Add a Copy Diagnostics action to the About screen

Puts the startup banner (version, channel, OS/JVM) and the last 200 lines
of the current log on the clipboard, so a bug report can carry its own
diagnostics instead of asking the reporter to find, zip, and attach a log.

The blob always includes the banner: a missing log costs the reporter the
log, not the whole report.

The buttons move into a FlowRow so three of them wrap on a narrow window.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Include the latest crash dump in Copy Diagnostics

* Only report the XDG session in the desktop banner on Linux

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 18:36:51 -07:00
Adam Brown
1bc1e37460
Fix the Microsoft Store build, and stamp builds with their distribution channel (#938)
* Switch the desktop Ktor engine to OkHttp

`java.net.http.HttpClient`'s constructor opens an NIO `Selector`, whose
Windows wakeup pipe is an AF_UNIX socket. The MSIX sandbox rejects the
connect with EINVAL, so every Microsoft Store build hard-crashes on the
first network call, before any request is sent (JDK-8312215, open since
Java 17 with no fix in sight). OkHttp uses blocking socket IO and is
unaffected.

Ships unconditionally rather than gated on the distribution channel:
OkHttp works everywhere, and a second engine would be a second code path
to test forever. Android already uses it.

Includes the unchecked-IO mapping from `fix/unchecked-io-network-errors`,
so a client that cannot be built at all surfaces as a network error
rather than taking down the app.

* Add an Export Logs button to the desktop About screen

Opening the log directory hands the shell a path that does not exist under
an MSIX container: the app's writes to %LOCALAPPDATA% are redirected into
the package's LocalCache, but reads fall through, so the app sees the
directory and Explorer does not. Snap confinement will do the same on
Linux. Exporting a zip to a location the user picks works on every
distribution vehicle, which makes it the one support instruction that
never needs a per-channel caveat.

Also walk up to the nearest existing ancestor before handing a directory
to the shell, so the open button degrades instead of erroring.

* Bake the distribution channel into the build

Per-vehicle rules (self-update, store payment policy, "get the app" links,
sandbox-aware paths) were tracked by hand. `-Pchannel=<token>` now resolves
to a `DistributionChannel` constant, defaulting to DEV, and an unknown
token fails the build rather than quietly shipping a store binary as DEV.
Every release pipeline passes its own channel; the existing F-Droid build
flags map to FDROID without their call sites changing.

Flat enum rather than capability flags: every channel-conditional branch is
a greppable `when` on the type. Flags can emerge later from actual
duplication.

The startup banner and all three crash dumps now carry the channel. That
line pays for itself immediately: this investigation started from a crash
log that did not say which build produced it.

* Un-redirect container paths before handing them to the shell

MSIX filesystem redirection is asymmetric: the app's writes under
%LOCALAPPDATA% land inside the package container, but reads fall through,
so File.exists() is true from inside the container and the app is
satisfied. Explorer runs outside it and correctly reports nothing at the
literal path, which is the "location is not available" dialog the
open-logs button produced on the Store build.

Rewrite the path into the container for the Microsoft Store channel, both
where it is shown and where it is handed to the shell. The package family
name is hardcoded: Windows derives its hash suffix from the publisher ID,
so it is not in AppxManifest.xml and cannot be computed from it, and it
only changes if the Store identity does.

`Windows.Storage.ApplicationData.Current.LocalCacheFolder` is the native
answer but means a WinRT dependency on a KMP desktop target for one path
lookup. Worth revisiting only if more packaged-app APIs are needed.

* Cover the About log section with a render test, document the channel

* Suppress TooGenericExceptionCaught on the unchecked IO catch
2026-09-14 15:38:15 -07:00