Commit graph

1495 commits

Author SHA1 Message Date
Adam Brown
1ba66394ab
New Crowdin updates (#1013)
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
* New translations from Crowdin

* New translations from Crowdin

* New translations from Crowdin

* New translations from Crowdin

* New translations from Crowdin

* New translations from Crowdin

* New translations from Crowdin

* New translations from Crowdin

* New translations from Crowdin
2026-10-18 00:40:53 -07:00
Adam Brown
c896edf379
Delete projects off the UI thread (#1011)
The delete dialog called deleteProject from its click handler, so the
metadata read, recursive delete and sync data write all blocked the UI
thread. Run them on the IO dispatcher.

---------

Co-authored-by: Sean Li <22557931+sean0407@users.noreply.github.com>
2026-10-17 23:34:11 -07:00
Adam Brown
9013716d0d
New Crowdin updates (#993)
* New translations strings.xml (French)

[ci skip]

* New translations strings_wear_pairing.xml (French)

[ci skip]

* New translations messages_en.properties (French)

[ci skip]

* New translations strings_scene_editor.xml (French)

[ci skip]

* New translations strings_scene_list.xml (French)

[ci skip]

* New translations strings_reader_mode.xml (French)

[ci skip]

* New translations strings_account_settings.xml (French)

[ci skip]

* New translations strings_project_home.xml (French)

[ci skip]

* New translations strings_about_app.xml (French)

[ci skip]

* New translations strings_encyclopedia.xml (French)

[ci skip]

* New translations strings_sync.xml (French)

[ci skip]

* New translations full_description.txt (French)

[ci skip]
2026-10-11 21:35:50 -07:00
Adam Brown
366e5b6a6f Prepared for release: v3.12.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-10-05 08:21:53 -07:00
Adam Brown
66e9920306
Keep unsaved scene buffers when the project scope closes (#983)
* Keep unsaved scene buffers when the project scope closes

Scope close deleted every temp buffer, so an Android activity destroyed in the background lost unsaved edits. Only clean buffers are cleared now, Discard discards explicitly, export reads unsaved buffers, and the Android back handler observes dirty state.

* Discard the buffer of an archived or deleted scene without reloading

* Save the buffer of an archived or deleted scene without throwing

* Drop a scene's unsaved buffer and temp file when it is deleted
2026-10-05 08:15:52 -07:00
Adam Brown
beab5e4271
New Crowdin updates (#972)
* Update source file Messages_en.properties

[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]

* New translations messages_en.properties (Turkish)

[ci skip]

* New translations strings.xml (French)

[ci skip]

* New translations strings.xml (Spanish)

[ci skip]

* New translations strings.xml (German)

[ci skip]

* New translations strings.xml (Italian)

[ci skip]

* New translations strings.xml (Ukrainian)

[ci skip]

* New translations strings.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings.xml (Turkish)

[ci skip]

* Update source file strings.xml

[ci skip]

* New translations strings_scene_editor.xml (French)

[ci skip]

* New translations strings_scene_editor.xml (Spanish)

[ci skip]

* New translations strings_scene_editor.xml (German)

[ci skip]

* New translations strings_scene_editor.xml (Italian)

[ci skip]

* New translations strings_scene_editor.xml (Ukrainian)

[ci skip]

* New translations strings_scene_editor.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_scene_list.xml (Italian)

[ci skip]

* New translations strings_scene_editor.xml (Turkish)

[ci skip]

* New translations strings_scene_list.xml (Turkish)

[ci skip]

* New translations strings_scene_list.xml (French)

[ci skip]

* New translations strings_reader_mode.xml (French)

[ci skip]

* New translations strings_scene_list.xml (Spanish)

[ci skip]

* New translations strings_reader_mode.xml (Spanish)

[ci skip]

* New translations strings_scene_list.xml (German)

[ci skip]

* New translations strings_reader_mode.xml (German)

[ci skip]

* New translations strings_reader_mode.xml (Italian)

[ci skip]

* New translations strings_reader_mode.xml (Turkish)

[ci skip]

* New translations strings_scene_list.xml (Ukrainian)

[ci skip]

* New translations strings_reader_mode.xml (Ukrainian)

[ci skip]

* New translations strings_scene_list.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_reader_mode.xml (Portuguese, Brazilian)

[ci skip]

* Update source file strings_reader_mode.xml

[ci skip]

* Update source file strings_scene_editor.xml

[ci skip]

* Update source file strings_scene_list.xml

[ci skip]

* New translations strings_account_settings.xml (Italian)

[ci skip]

* New translations strings_account_settings.xml (Turkish)

[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 (Ukrainian)

[ci skip]

* New translations strings_account_settings.xml (Portuguese, Brazilian)

[ci skip]

* Update source file strings_account_settings.xml

[ci skip]

* New translations strings_project_home.xml (Italian)

[ci skip]

* New translations strings_project_home.xml (Turkish)

[ci skip]

* New translations strings_project_home.xml (French)

[ci skip]

* New translations strings_project_home.xml (Spanish)

[ci skip]

* New translations strings_project_home.xml (German)

[ci skip]

* New translations strings_project_home.xml (Ukrainian)

[ci skip]

* New translations strings_project_home.xml (Portuguese, Brazilian)

[ci skip]

* Update source file strings_account_settings.xml

[ci skip]

* Update source file strings_project_home.xml

[ci skip]

* New translations strings_account_settings.xml (Italian)

[ci skip]

* New translations strings_account_settings.xml (Turkish)

[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 (Ukrainian)

[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 (Italian)

[ci skip]

* New translations strings_account_settings.xml (Italian)

[ci skip]

* New translations strings_about_app.xml (Turkish)

[ci skip]

* New translations strings_account_settings.xml (Turkish)

[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 (Ukrainian)

[ci skip]

* New translations strings_account_settings.xml (Portuguese, Brazilian)

[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 (Ukrainian)

[ci skip]

* New translations strings_about_app.xml (Portuguese, Brazilian)

[ci skip]
2026-10-05 08:11:46 -07:00
Adam Brown
a90b72d291
Gate sync behind the unsaved-work dialogs and make Discard really discard (#987)
* Gate sync behind the unsaved-work dialogs and make Discard really discard

Sync flushed every dirty scene buffer in prepareForSync, so an automatic sync on
close committed scenes the user had just chosen to discard, and manual syncs
silently saved everything. startProjectSync now builds the same confirmation set
as requestClose (scenes, notes, encyclopedia, timeline) and ends in Sync instead
of Complete, so the existing dialogs run before the sync modal opens.

The Discard buttons previously only cleared the confirmation; they now revert the
scene buffers to disk and drop in-progress entity edits (or pop a create screen)
through a new Router.discardUnsaved hook.

* Close the remaining ungated sync paths and harden discard

Route the Home screen's Sync action through startProjectSync so it hits the
gate, ignore startProjectSync while a close request is pending, and stop the
scene synchronizer from persisting dirty buffers in prepareForSync and
finalizeSync: upload content and hashes already come from disk, and every
interactive sync is now confirmed up front, so the flush only ever committed
buffers nobody was asked about (crash-recovery buffers during the startup
account sync, for one).

ProjectRoot.discardUnsaved is now a suspend call that reverts scene buffers off
the main thread, and the entity components check and discard across their
whole stack rather than only the active destination.
2026-10-05 08:10:43 -07:00
Adam Brown
ff622e82d8
Refresh open views when their entity changes underneath them (#984)
* Refresh open views when their entity changes underneath them

Note, timeline event, encyclopedia entry and scene metadata views loaded
once and went stale after sync. They now follow their repository flows,
keeping an in-progress edit and only moving its baseline.

* Address review findings on open-view refresh

- An untouched open edit follows the synced change; only a dirty edit keeps its draft
- Entry reloads are serialized, survive I/O errors, and only run for the open entry
- A note deleted elsewhere closes its view unless a dirty edit is open
- Synced scene metadata is no longer marked dirty or written back by the panel
- Sync keeps the local draft name; reference use cases name their update source

* Catch specific exceptions in the entry refresh
2026-10-05 08:09:05 -07:00
Adam Brown
28b4df9b4a
Offer in-app updates on the GitHub desktop builds (#978)
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
* Offer in-app updates on the GitHub desktop builds

AppUpdater in :common with a no-op for every store build, and a Nucleus
backed NucleusAppUpdater in :desktop that checks shortly after launch and
every six hours while the new automaticUpdateChecks setting allows it. One
action, update(), downloads, verifies and hands the installer over; MSI,
EXE, DEB, RPM and AppImage installs relaunch on the new version, macOS
installs are told and sent to the release page until the signed-zip work
lands. A notice strip above the project list, a status row on the About
screen, and a settings toggle, each with previews and render tests.

* Keep an offered update on screen while it is re-checked

A check only shows Checking when nothing better is known, so the six-hourly
re-check no longer blinks the banner away. The updater's shared fields are
volatile, the banner and the About row share one state-to-text mapping,
the download test hands the installer over through a CompletableDeferred,
and the desktop platform settings component gets a test for the new toggle.

* Lay the update notice on the page and allow an environment override

The banner now renders inside the project list under the masthead, inset
to the page gutter, instead of as a strip above the whole content pane
butting against the nav rail. HAMMER_UPDATE_BASE_URL stands in for the
system property so an installed build can be pointed at a local server,
which is how the update from a 3.10.99 test build to the published 3.11.0
MSI was exercised end to end. The download test cleans up its staging
directory, as the real installer script does.

* Give the Android project selection preview fake the update members
2026-10-05 03:09:55 -07:00
Adam Brown
385a6f28d2
Fix backup restore nesting the project inside itself (#982)
* Fix backup restore nesting the project inside itself

Backup archives are rooted at the project's directory name, so unzipping
into the project directory left it without a project.toml at its root and
the project vanished. Restore now extracts to a staging directory and
swaps the unwrapped project into place, leaving the existing project
untouched if extraction fails.

The Backup Manager also lists backups of projects that no longer exist,
and reports a failed restore.

* Harden backup restore swap and missing-project listing

Move the existing project aside instead of deleting it so a failed swap
puts it back, serialize restores that share the staging directory, and
list a missing project once when it has both legacy and current backup
names.
2026-10-04 23:11:57 -07:00
Adam Brown
2b4b1698b3 Version bumps 2026-10-04 22:54:10 -07:00
Adam Brown
4f2004431f
Stamp builds with their package format and publish update metadata (#977)
* Stamp builds with their package format and publish update metadata

-Pformat=<token> is resolved in buildSrc, baked into BuildMetadata, and
printed in the startup banner next to the channel; every packaging step
passes its own. The MSI UpgradeCode is pinned to the value every installed
copy already carries. writeUpdateMetadata renders latest*.yml from the
attached installers and the release workflow uploads them to the draft
before it publishes. HammerReleaseProvider points the Nucleus updater at
releases/latest/download so partial-release tags cannot break downloads.

* Hash installers where they are built instead of re-downloading them

Each platform job now writes a <installer>.sha512 sidecar beside what it
attaches and hands the sidecars to the final job as a workflow artifact.
That job assembles latest*.yml from them and attaches the yml with the same
release action every other job uses, so nothing is downloaded back from the
draft and nothing depends on looking a draft release up by tag.
2026-10-04 22:45:07 -07:00
Adam Brown
56e6e82b82
Fix PDF export cutting off lines at page breaks (#981)
pdfkmp cannot split rich text, rows or decorated containers across
pages, so prose is wrapped into lines up front and laid out one node
per line.
2026-10-04 22:36:24 -07:00
Adam Brown
215fbf6537
Add export options for chapter numbering, font and blank lines (#980)
Three new options in the Export Story dialog, from user feedback on Word, EPUB and PDF exports.

- Number chapter titles: off by default for every format. On prefixes each title with its position in the headings and the Contents page. Word, RTF, PDF and Markdown used to number always; EPUB never did and now can.
- Font: Georgia, Times New Roman, Garamond, Arial, Verdana or Courier New for Word, RTF and EPUB. PDF keeps its built-in face.
- Keep blank lines between paragraphs: on by default. Off drops a lone blank line so the indent is the only separator; a run of two or more, and the boundary between scenes, still leaves one break.
2026-10-04 22:17:45 -07:00
Adam Brown
2ad0322067
Derive the Mac App Store build mode from the distribution channel (#975)
* Derive the Mac App Store build mode from the distribution channel

The App Store build was detected two ways: Gradle read -PmacOsAppStoreRelease
for packaging and passed -Dhammer.app.store=true so IS_APP_STORE could gate
runtime behaviour. The channel enum already knows the same fact from
-Pchannel=mac-app-store, which build-appstore.sh passed alongside.

isAppStoreRelease now resolves from the channel token, the JVM property and
IS_APP_STORE are gone, and the three runtime readers (sandbox startup, the
bookmark-backed file access binding, the in-app changelog gate) branch on
DistributionChannel.current like every other per-vehicle rule.

* Share the channel resolver across build scripts and name the App Store check

Project.distributionChannel() in buildSrc owns the -Pchannel and F-Droid
detection, so :base and :desktop cannot disagree on the channel.
DistributionChannel.isMacAppStore replaces three spelled-out comparisons,
and supportsInAppChangelog is a pure function of the channel with a test
pinning the table. Docs corrected: section 1 is done, GitHubProvider only
uses the REST API for pre-release channels, and the App Store build also
needs -PbuildNumber.
2026-10-04 22:11:24 -07:00
Adam Brown
ba28b01b82
Add OLED black theme (#979)
Dark theme with a pure black background and base surface. Applies to project color themes and Android dynamic color too.

Offer the OLED theme on Android and iOS only
2026-10-04 17:04:36 -07:00
Adam Brown
d884591bf6
Add Reader Mode (#976)
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
Read the live story as prose, one chapter at a time, from the story
editor details pane. Opens from the scene list footer, the scene editor
top bar, or the editor menu.

- Scenes in a chapter are stitched together with an ornament between
  them; a toggle shows scene headings instead.
- While the reader is open the scene list works as a table of contents,
  and its highlight follows the scene being read.
- Reader font size is its own setting. Text is selectable.
- Chapter and scene position survive rotation and process death.

Chapter collection is now shared with export, and the outline's chapter
pieces move into the design system. MarkdownView gains opt-in
importDuringComposition and isSelectable. The footer's outline button
uses a new short "Outline" string.
2026-10-03 23:59:24 -07:00
Adam Brown
ca22ec262d
ComposeTextEditor 3.0.2 and a render-time editor text size (#973)
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
* Update ComposeTextEditor to 3.0.2

Styles move to TextEditorState.richTextStyles, headings use the block API so
they export as headings, and shortcuts match on layoutKey. Markdown pins
ParagraphSeparator.NEWLINE to keep the 2.8 on-disk format.

* Make the editor text size a render property

Rich text styles are a fixed set with em-relative headings, so the body size
lives on the editor text style and changing it rewrites no document. The
size steps through a ladder, shows its value in the format bar menu, and
answers Ctrl+Plus, Minus and 0.
2026-10-03 13:36:53 -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
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
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
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
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
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
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
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
72304dfa87 Prepared for release: v3.9.7 2026-09-04 20:08:54 -07:00
Wavesonics
e833d57f8d Prepared for release: v3.9.6
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-03 20:25:54 -07:00
Wavesonics
9fd796c342
Prepared for release: v3.9.5 2026-09-01 20:51:00 -07:00
Wavesonics
4ad20162f4
Squash merge no-in-app-changelog-on-apple into develop 2026-09-01 20:27:32 -07:00
Adam Brown
aa51c311dc Prepared for release: v3.9.4
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-08-22 17:48:02 -07:00
Adam Brown
92c0aaa3da Prepared for release: v3.9.3
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-22 10:00:40 -07:00
Adam Brown
d1ba0c6456 Prepared for release: v3.9.2
Some checks are pending
Build CI / iOS UI tests (push) Waiting to run
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
PublishInternal / publish-google-play (push) Waiting to run
2026-08-21 23:50:57 -07:00
Adam Brown
d424a346ed Prepared for release: v3.9.1
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-08-18 00:02:23 -07:00
Wavesonics
bdc2ea6966
Count probe-skipped projects in the overall sync progress bar
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-17 21:49:57 -07:00
Adam Brown
be04256597
New Crowdin updates (#911) (#920)
* 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]

* 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]

* New translations strings_project_home.xml (French)

[ci skip]

* New translations strings_project_home.xml (Spanish)

[ci skip]

* New translations strings_project_home.xml (German)

[ci skip]

* New translations strings_project_home.xml (Italian)

[ci skip]

* New translations strings_project_home.xml (Ukrainian)

[ci skip]

* New translations strings_project_home.xml (Portuguese, Brazilian)

[ci skip]

* Update source file strings_project_home.xml

[ci skip]

* Update source file Messages_en.properties

[ci skip]

* Update source file strings_account_settings.xml

[ci skip]

* Update source file strings_encyclopedia.xml

[ci skip]

* Update source file strings_sync.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 strings_encyclopedia.xml (French)

[ci skip]

* New translations strings_encyclopedia.xml (Spanish)

[ci skip]

* New translations strings_encyclopedia.xml (German)

[ci skip]

* New translations strings_encyclopedia.xml (Italian)

[ci skip]

* New translations strings_encyclopedia.xml (Ukrainian)

[ci skip]

* New translations strings_encyclopedia.xml (Portuguese, Brazilian)

[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]
2026-08-17 21:43:18 -07:00
Adam Brown
d39819fbe6 Prepared for release: v3.9.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-08-16 22:38:10 -07:00
Adam Brown
8d5899bd46 Address review findings on encyclopedia dictionary
- Conflict resolution: onEncyclopediaEntryConflict now carries aliases and
  excludeFromDictionary into the local entity; "keep local" was stripping
  both, and with the flag in the entity hash that meant a conflict that
  could never converge (the alias drop predates this feature)
- ViewEntryComponent: one saveEntry(transform) helper replaces six
  hand-threaded updateEntry call sites and loads content from disk when
  the async state load hasn't landed, so a save can no longer revert
  aliases or the exclusion flag to defaults
- SpellCheckRepository: SupervisorJob + guarded checker calls so a
  platform failure can't kill the settings collector; appliedCandidates
  recorded only after a successful apply so failures retry
- ProjectDictionaryService: initialize() instead of init side effects,
  cancel-before-clear plus an isActive check to keep a racing rebuild
  from re-adding a closed project's words, spellCheckSettings.enabled
  added to the rebuild gate, injectDefaultDispatcher(), and
  loadEntriesImperative() now returns the defs
- temporaryProjectTask scopes skip the dictionary service, ending the
  checker churn during bulk sync and import
- HdHairlineToggleRow gains an enabled param (semantic disable, no
  ripple) replacing three hand-rolled Box+alpha idioms
2026-08-16 20:50:41 -07:00
Adam Brown
5766d2b532
Connect scenes to every encyclopedia entry type (#917)
ReferenceIndexConfig.default() now enables all five EntryTypes, so Thing,
Event, and Idea entries get linked to the scenes that mention them. The
metadata panel already bucketed refs into Characters/Locations/Other, so
the new types land in Other with their own glyphs.

Add-reference search takes a type set and applies it before the result
cap, so one tab can no longer starve another out of the slice. The
appearances chart filters to PERSON to stay true to its label; the
separate connections total still spans all types.
2026-08-16 20:47:47 -07:00