Commit graph

3554 commits

Author SHA1 Message Date
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
Adam Brown
4b2dff8c23
Map unchecked IO failures to a real error message (#933)
* Map unchecked IO failures to a real network error message

The JDK builds its `java.net.http.HttpClient` lazily on the first request,
and `HttpClientImpl`'s constructor wraps a failed `Selector.open()` in an
`UncheckedIOException`. On Windows that selector is a loopback socket pair,
so a machine with a firewall, VPN filter driver, or exhausted ephemeral port
range fails with `java.io.IOException: Unable to establish loopback
connection` before a request is ever sent.

`UncheckedIOException` is a `RuntimeException`, so it slipped past every
catch in `Api.makeRequest` and surfaced in the sync log as a raw Java class
name. Route it through the same mapping, with a dedicated message: nothing
reached the network, so this must not read as a server or connectivity
problem.

* Only log "Sync complete!" when the sync actually succeeded

`handleSyncFailure` calls `onComplete`, which logged the success line
unconditionally, so a failed sync ended with an error immediately followed
by "Sync complete!". Thread the outcome through `onComplete` and gate the
log on it; the progress reset still runs either way.

* Suppress TooGenericExceptionCaught on the unchecked IO catch
2026-09-14 15:38:11 -07:00
Adam Brown
ca909f4a6b
Add a shared network JSON serializer that tolerates unknown keys (#934)
`SYNCING-PROTOCOL.md` makes adding a field to a synced model a client-only
change: the server stores content as an opaque blob, and a peer that predates
the field is expected to drop it on decode. That only holds if the decode
ignores unknown keys.

Every `ContentNegotiation` install used a bare `json()`, which is Ktor's
`DefaultJson`: `encodeDefaults` and `isLenient`, but no `ignoreUnknownKeys`. So
instead of dropping the field, an older peer failed the whole sync. A 3.6.0
client hitting project data written by 3.9 got:

    Sync failed: Illegal input: Unexpected JSON token at offset 72:
    Encountered an unknown key 'language' at path: $.data

`createJsonSerializer` already sets `ignoreUnknownKeys`, but it is meant for
human-facing files: `prettyPrint` would put tabs and newlines in every request
body, and `coerceInputValues` would silently coerce bad values on the wire.
Split the two: files keep that one, machine-facing content gets
`createNetworkJsonSerializer`, resolved through `NetworkJsonQualifier`.

Applies to the three `ContentNegotiation` installs and the four classes that
decode wire content with an injected `Json`: `ServerProjectApi` (entity
payloads), `ProjectDataApi` and `ServerIdeasApi` (conflict bodies), and
`GithubVersionCheckDataSource`. The file-facing injections are unchanged.

This cannot reach already-shipped builds; 3.6 through 3.9.x keep failing
against projects a newer client has touched.
2026-09-14 15:33:32 -07:00
Adam Brown
375d4553c6
Stop credential fields from auto-capitalizing what is typed (#936)
* Stop credential fields from auto-capitalizing what is typed

HdHairlineField defaults to KeyboardCapitalization.Sentences, and neither the
server setup dialog nor the reauthentication dialog overrode it on their email or
password fields. An IME that honours CAP_SENTENCES on a password variation
capitalizes the first character as it is entered, so the account is created with
a password the user cannot reproduce in a browser, where nothing capitalizes
anything. The failure looks exactly like a rejected password.

HdPasswordField and HdEmailField pin the keyboard options that credential entry
needs (no auto-capitalization, no autocorrect) so a screen cannot forget them.
HdPasswordField also absorbs the show/hide toggle that both dialogs had inlined
identically, and carries the 8 to 64 character rule as a field hint, formatted
from PasswordValidator so it cannot drift from what the server enforces.

* Add a design-system preview for the credential fields

Covers the password field masked with its length hint, revealed with an error,
and the email field beside them.
2026-09-14 15:33:17 -07:00
Sam Goldman
dd00aa12bf
Fix Community Pagination Links (#942)
* Fix previous and next page links on community authors page

* Fix previous and next page links on community feed page
2026-09-14 15:29:01 -07:00
Adam Brown
d0331c45d7
Fail fast on App Store Connect validation errors (#941)
deliver_with_retry treated every error as transient. It exists for App Store
Connect's eventual consistency around build lookup and attachment, but a
validation failure will fail identically forever — the first 3.9.7 iOS submit
spent all six attempts and five minutes restating "App screenshot missing"
before surfacing it.

Errors matching a narrow set of validation patterns now raise immediately. The
list is deliberately narrow: anything unmatched still retries, since a new
permanent error costs a few wasted attempts while a misclassified transient one
costs a failed release. Matching is on message text rather than exception class
because Spaceship raises UnexpectedResponse for transient faults too.

Permanent errors now print the whole message. App Store Connect puts the
summary on line 1 and the actual reasons underneath, and the retry logging only
ever printed the first line, which is part of why the real cause was hard to
see.

submission_landed? still runs first, so the case where deliver raises after the
submission already landed is unaffected.
2026-09-14 15:28:37 -07:00
Wavesonics
59d1537950
Prepared for release: v3.9.8
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
2026-09-12 00:54:08 -07:00
Wavesonics
1c2bdfafd5 Lay the iOS screenshots out the way deliver actually reads them
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
Setting screenshots_path was still not enough. deliver globs
<screenshots_path>/<locale>/*.png flat and does not descend into per-device
subdirectories — only appleTV and iMessage are special-cased. The images were
in en-US/APP_IPHONE_65/ and en-US/APP_IPAD_PRO_3GEN_129/, one level too deep,
so deliver kept finding an empty set and reporting success for uploading none.

Those APP_* folder names are App Store Connect API display types, which deliver
never reads: it derives the device from the image's pixel dimensions. So the
files now sit flat in fastlane/screenshots/ios/en-US/, out of the metadata tree
entirely, and screenshots_path points there.

Filenames avoid "app_ipad_pro_129" and the "12.9"/"2nd generation" pair,
because 2732x2048 is ambiguous between the 2nd- and 3rd-gen iPad Pro and
deliver disambiguates on the path. Without those strings it resolves to
APP_IPAD_PRO_3GEN_129, which is what App Store Connect asks for.

Verified against deliver 2.237.0's own glob and resolution table: 15 images
discovered, 7 APP_IPHONE_65 and 8 APP_IPAD_PRO_3GEN_129, none unmapped.
2026-09-04 23:12:03 -07:00
Wavesonics
003cf0a013 Point deliver at the iOS screenshots so the submit can pass
Turning off skip_screenshots was not enough on its own. deliver keeps
screenshots_path as a separate option from metadata_path and defaults it to
./fastlane/screenshots, which this repo does not have — the images live beside
the metadata under fastlane/metadata/ios/<locale>/APP_*.

So deliver looked at an empty set, reported "Successfully uploaded all
screenshots" having uploaded none, and the v3.9.7+ios-app-store submit failed
with "App screenshot missing (APP_IPHONE_65)". The iOS platform newly added to
the macOS record starts with no screenshots, so there was nothing already in
App Store Connect to fall back on.

overwrite_screenshots is set because deliver_with_retry can call deliver
several times, and each attempt would otherwise add another copy of every
screenshot to the version.
2026-09-04 22:59:23 -07:00