Commit graph

272 commits

Author SHA1 Message Date
Adam Brown
c8eb4b874d
New Crowdin updates (#847)
* Update source file strings.xml

[ci skip]

* Update source file strings_about_app.xml

[ci skip]

* Update source file strings_account_settings.xml

[ci skip]

* Update source file strings_desktop.xml

[ci skip]

* Update source file strings_drafts.xml

[ci skip]

* Update source file strings_encyclopedia.xml

[ci skip]

* Update source file strings_globalsearch.xml

[ci skip]

* Update source file strings_ideas.xml

[ci skip]

* Update source file strings_notes.xml

[ci skip]

* Update source file strings_project_home.xml

[ci skip]

* Update source file strings_project_navigation.xml

[ci skip]

* Update source file strings_project_select_navigation.xml

[ci skip]

* Update source file strings_projects_list.xml

[ci skip]

* Update source file strings_scene_editor.xml

[ci skip]

* Update source file strings_scene_list.xml

[ci skip]

* Update source file strings_sync.xml

[ci skip]

* Update source file strings_timeline.xml

[ci skip]

* Update source file strings_android.xml

[ci skip]

* Update source file full_description.txt

[ci skip]

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

* 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 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]
2026-08-03 23:17:09 -07:00
Adam Brown
1b312bc3b2
Allow a project to set the language it's written in (#838)
* Add per-project language setting (#754)

An optional BCP-47 language on ProjectData, picked from a searchable
list of all platform locales in project settings. New projects default
to the device locale; the Alice example project is en-US.

Spell check is gated per project: when the project language does not
leniently match the dictionary locale, the dictionary is withheld
(ProjectSpellCheckRepository) and project settings explain why.

The public story page emits <html lang> and JSON-LD inLanguage from the
declared language, and EPUB export prefers it over the device locale.
The hasher contributes zero bytes when unset so existing sync hashes
stay stable.

* Fix review findings in the project-language feature

createProject now only seeds the default language for genuinely new
projects (seedDefaultLanguage), so account sync materializes server
projects with the never-synced baseline intact, and the seed is
language-only so it cannot gate spell check against a same-language
dictionary. The hasher's language block gets a -1 marker plus length
prefix so it can never collide with a tags block, and the initial
write goes through the shared saveStoredProjectData path.

The Locale type now retains the script subtag, keeping zh-Hans/zh-Hant
style locales distinct in the picker. The picker's clear row is pinned
above the list so it survives an empty search, watchSpellCheckAllowed
delivers on the main dispatcher, and the public story page hashes the
stored project-data hash into its validator instead of parsing the
blob per request, applying the language override after withDefaults so
chrome links keep the viewer's locale.

* Enforce single-owner persisted formats

The tags write in PromoteIdeaUseCase rewrote project_data.toml from
scratch, erasing the language seed createProject had just written: the
exact hazard of a second inline writer. It now read-modify-writes
through the datasource's scope-less helpers, and ProjectsListComponent's
hand-rolled reader delegates to a new blocking readStoredProjectData.

The rule is written down (ARCHITECTURE.md hard constraint 7, CLAUDE.md)
and enforced by PersistedFormatOwnershipTest, which fails the build when
raw TOML I/O appears outside a Datasource file. Migrators are exempt by
role; the two remaining legacy offenders are allowlisted as a burn-down
that can only shrink.

* Burn down the last raw TOML I/O outside datasources

ProjectStatisticsCacheReader now delegates to a scope-less
readProjectStatistics helper in StatisticsDatasource, and the example
project's fabricated activity log goes through writeDeviceLog in
WritingActivityDatasource, which also becomes the single owner of the
.activity path convention.

With no offenders left, PersistedFormatOwnershipTest drops its
burn-down allowlist entirely: only Datasource files and migrators may
touch persisted TOML formats from here on.

* Pass seedDefaultLanguage in the Android instrumented-test harness

* Pass seedDefaultLanguage in the round-trip sync HeadlessClient
2026-08-03 22:22:00 -07:00
Adam Brown
ad2553908b
New Crowdin updates (#843)
* Update source file strings.xml

[ci skip]

* Update source file strings_about_app.xml

[ci skip]

* Update source file strings_account_settings.xml

[ci skip]

* Update source file strings_desktop.xml

[ci skip]

* Update source file strings_drafts.xml

[ci skip]

* Update source file strings_encyclopedia.xml

[ci skip]

* Update source file strings_globalsearch.xml

[ci skip]

* Update source file strings_ideas.xml

[ci skip]

* Update source file strings_notes.xml

[ci skip]

* Update source file strings_project_home.xml

[ci skip]

* Update source file strings_project_navigation.xml

[ci skip]

* Update source file strings_project_select_navigation.xml

[ci skip]

* Update source file strings_projects_list.xml

[ci skip]

* Update source file strings_scene_editor.xml

[ci skip]

* Update source file strings_scene_list.xml

[ci skip]

* Update source file strings_sync.xml

[ci skip]

* Update source file strings_timeline.xml

[ci skip]

* Update source file strings_android.xml

[ci skip]

* Update source file full_description.txt

[ci skip]

* 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 strings.xml (French)

[ci skip]

* New translations strings_about_app.xml (French)

[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_desktop.xml (French)

[ci skip]

* New translations strings_drafts.xml (French)

[ci skip]

* New translations strings_encyclopedia.xml (French)

[ci skip]

* New translations strings_notes.xml (French)

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

* New translations strings_project_navigation.xml (French)

[ci skip]

* New translations strings_project_navigation.xml (Spanish)

[ci skip]

* New translations strings_project_navigation.xml (German)

[ci skip]

* New translations strings_project_navigation.xml (Italian)

[ci skip]

* New translations strings_project_navigation.xml (Ukrainian)

[ci skip]

* New translations strings_project_navigation.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_projects_list.xml (French)

[ci skip]

* New translations strings_scene_editor.xml (French)

[ci skip]

* New translations strings_scene_list.xml (French)

[ci skip]

* New translations strings_sync.xml (French)

[ci skip]

* New translations strings_timeline.xml (French)

[ci skip]

* New translations strings_timeline.xml (Spanish)

[ci skip]

* New translations strings_timeline.xml (German)

[ci skip]

* New translations strings_timeline.xml (Italian)

[ci skip]

* New translations strings_timeline.xml (Ukrainian)

[ci skip]

* New translations strings_timeline.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_android.xml (French)

[ci skip]

* New translations strings_android.xml (Spanish)

[ci skip]

* New translations strings_android.xml (German)

[ci skip]

* New translations strings_android.xml (Italian)

[ci skip]

* New translations strings_android.xml (Ukrainian)

[ci skip]

* New translations strings_android.xml (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 (Italian)

[ci skip]

* New translations full_description.txt (Ukrainian)

[ci skip]

* New translations short_description.txt (French)

[ci skip]

* New translations short_description.txt (Spanish)

[ci skip]

* New translations short_description.txt (Italian)

[ci skip]

* New translations short_description.txt (Ukrainian)

[ci skip]

* New translations strings_globalsearch.xml (French)

[ci skip]

* New translations strings_ideas.xml (French)

[ci skip]
2026-08-03 22:07:22 -07:00
Adam Brown
804d9cb75c
Harden the remaining E2E workflow clicks against dropped taps (#822)
Reuse clickUntil for the navigational and menu clicks in the timeline,
scene-list, scene-editor and project-lifecycle tests. Also waits for the
add-menu to open before picking an item, which previously ran with no wait
at all.

Mutating confirms are left as single clicks: those buttons stay on screen
across the async create, so a retry would persist a second item.

* Make clickUntil prove the target existed before reporting success

clickUntil skips the click while its matcher resolves to nothing, so a landed
condition that is already true returned success having clicked nothing, and a
target that never rendered failed as "click never took effect".

Track whether a click was injected: never return without one, and report a
target that never appeared as its own failure. Drops the per-site pre-waits
that duplicated this.
2026-08-01 17:01:18 -07:00
Adam Brown
23502a6ea1
Stop the encyclopedia E2E test failing on a dropped tap (#817)
createEntryThenOpenIt intermittently timed out waiting for the create FAB
to disappear: the tap on the new entry card never became an onClick, so the
browse list never navigated.

The card is present, displayed and correctly positioned when this happens.
Its centre resolves to the card's own clickable rather than the type stamp
or a tag chip, the outgoing create screen is gone well before the card
appears, and the card's bounds are stable from the frame it renders. Only
the software renderer CI uses reproduces it, at roughly one run in six.

clickUntil re-injects the click until the expected outcome shows up, the
same way typeIntoEditor already re-injects keystrokes. It stops re-clicking
once the matcher resolves to nothing, so a slow but successful navigation
does not get a second click on the next screen.
2026-08-01 13:40:13 -07:00
Adam Brown
54538da8a6
Stop the E2E teardown racing the project-scope flush (#780)
The instrumented tests delete their project directory in teardown, but
finish() and waitForIdleSync() only drain the main thread. The Koin
project-scope close flushes scene buffers on IO coroutines after that, so a
straggler write can land between deleteRecursively emptying the directory and
removing it. The rmdir then fails with "failed to delete <project dir>".

Seen on develop in both ProjectLifecycleTest and SceneEditorWorkflowTest, so
it is the shared teardown rather than any one test.

There is no signal to await for that scope close, so retry the delete until
the flushes stop. Each attempt removes whatever reappeared, and it still fails
loudly after 15s rather than hiding a real leak.

ProjectLifecycleTest now uses the same helper instead of deleting inline.
2026-07-27 14:35:52 -07:00
Adam Brown
34af8d3013
Rename the Android string resources to strings_android.xml (#776)
Crowdin's Custom Post-Export Processor only receives a file's name, not its
path, so it could not tell the Android resources apart from the Compose ones
while both were called strings.xml. Everything Crowdin manages under
android/src/main/res/ now carries the _android.xml suffix, which the export
snippet uses to leave AAPT's quote escaping intact.

AAPT does not care what the file is called, and crowdin.yml already globs
strings*.xml, so neither needed changing.
2026-07-27 08:49:05 -07:00
Adam Brown
5b17fbf429
Make clients HTTPS-only; add dev self-signed cert support (#743)
Remove the HTTP/HTTPS protocol picker and all ssl plumbing from the
client. Persisted server settings now always resolve to HTTPS, so a
legacy ssl=false server.json is upgraded on load. ServerSettings.ssl is
kept only as an internal seam for the plain-HTTP integration-test server.

Android: drop the permissive network_security_config so cleartext
traffic uses the secure platform default (blocked).

Server keeps its plain HTTP connector for reverse-proxy deployments. In
--dev with no sslCert configured, it now generates and persists a
self-signed keystore (hammer_data/dev-selfsigned.jks) and serves TLS on
a non-privileged port (8443 by default). The desktop --dev client trusts
that cert for loopback hosts only; remote hosts still get full cert and
hostname validation.
2026-07-17 00:52:41 -07:00
Wavesonics
89447fc4c4
FileLogger will now include crash stack traces on Android and Desktop 2026-07-14 22:01:37 -07:00
Adam Brown
e8c87d049a Fix Encyclopedia image selection on Android
The suspend FileKit dialog API resolves the ActivityResultRegistry from a
global set only by FileKit.init(). It was never called on any activity, so
the picker in ProjectRootActivity (encyclopedia) either targeted a stale
registry (result never delivered) or threw and was silently swallowed.
Call FileKit.init(this) in both activities' onCreate.

Once the picker returned a file, readExternalFile crashed: stageIntoCache
hands it a plain cache file path, but AndroidExternalFileIo forced every
path through ContentResolver (content:// only). Branch on URI scheme so
file paths are read directly, matching desktop/iOS.

Concise alternative if you prefer a shorter body:

Fix Encyclopedia image selection on Android

- Call FileKit.init(this) in both activities so the suspend picker API has
a live ActivityResultRegistry; without it the encyclopedia picker failed
silently.
- AndroidExternalFileIo.readExternalFile now handles plain file paths (not
just content:// URIs), fixing the crash on the staged cache path.
2026-07-12 23:24:59 -07:00
Adam Brown
bd9c94376a Merge branch 'develop' into l10n_develop
# Conflicts:
#	common/src/commonMain/composeResources/values-zh-rCN/strings.xml
#	common/src/commonMain/composeResources/values-zh-rCN/strings_account_settings.xml
#	common/src/commonMain/composeResources/values-zh-rCN/strings_desktop.xml
#	common/src/commonMain/composeResources/values-zh-rCN/strings_encyclopedia.xml
#	common/src/commonMain/composeResources/values-zh-rCN/strings_projects_list.xml
#	common/src/commonMain/composeResources/values-zh-rCN/strings_scene_list.xml
#	common/src/commonMain/composeResources/values-zh-rCN/strings_sync.xml
#	server/src/main/resources/i18n/Messages_zh_CN.properties
2026-07-12 16:00:13 -07:00
Adam Brown
5117369168 Remove Chinese support
The text editor does not current support RTL languages, or even non-latin alphabets. So this doesn't make sense in the rest of the UI currently.
2026-07-12 12:14:00 -07:00
Adam Brown
94dade3b1f New translations strings.xml (Spanish)
[ci skip]
2026-07-10 22:47:43 -07:00
Adam Brown
6803dc55a0 New translations strings.xml (French)
[ci skip]
2026-07-10 18:33:51 -07:00
Adam Brown
b4e4a08702
Story Ideas: account-level idea capture with sync (#720)
Quick-capture story ideas as tagged markdown blobs in a new Project
Selection tab, stored one file per idea in .ideas/ and promotable into
a project. Offline-first; syncs as a phase inside the account sync
session (shape-agnostic server storage, hash-baseline conflicts,
tombstone/outbox deletion, ideasStateHash skip for unchanged sets).
Unifies idea + project tag suggestions behind AccountTagService.
2026-07-05 01:49:04 -07:00
Wavesonics
8bcd696761
Add startup logging: build/environment banner and startup breadcrumbs
Log a single identifying line as the first line on every platform:
version plus OS/runtime, and on desktop the display server (Wayland/X11
+ DE) and Skia renderApi. Makes user-submitted logs self-describing.

Banner is expect/actual in :common, wired into the desktop, Android, and
iOS startup entry points. Also adds INFO breadcrumbs across desktop
startup so a stall localizes to a stage.
2026-07-01 22:04:11 -07:00
Adam Brown
092652edfa Add RTF import for new projects
Adds RTF as an import format alongside Markdown. RTF has no headings, so
the importer collects each paragraph with its formatting and splits into
scenes by one of three user-chosen strategies: formatting (outline level
/ font size / bold), a chapter-heading regex, or a single scene.

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

Markdown gains an Auto detection mode (the new default) that reads a lone
leading heading as the project title and picks the chapter level by
frequency. The create-dialog help affordance becomes an icon button.
2026-06-25 00:15:37 -07:00
Adam Brown
4e0cee48c8 Move story import into the project creation flow
Relocate "Import Story" from the Project Home screen to the Create
Project dialog. An "Import" action in the create dialog opens the file
picker and import dialog; confirming creates the project (name derived
from the file name, editable and validated) and splits the file into
scenes by running the existing ImportStoryUseCase in a temporary
project scope.

- Move import state/handlers from ProjectHome(Component) to
  ProjectsList(Component)
- Move ImportStoryDialog + ImportFilePicker (expect/3 actuals) to the
  projectselection package; picker is now callback-based
- Add an optional mastheadAction slot to FormDialog for the Import button
- Add an editable, validated project-name field to the import dialog
- Add ProjectsListComponent tests for the import handlers and the
  create-failure path
2026-06-24 17:29:18 -07:00
Adam Brown
3e9ad32109 Unexport AddNoteActivity and filter obscured touches (F-8)
AddNoteActivity was exported with no permission, so any installed app could pop the Add Note dialog over the user pre-locked to a chosen project; a tapjacking overlay tricking a Save tap would write a note and push it to the sync server. It is only ever launched in-process (ProjectSelectActivity) or via the home-screen widgets' PendingIntents, neither of which needs export. Set android:exported=false in both manifests and filter touches delivered while the window is obscured.
2026-06-24 16:09:47 -07:00
Adam Brown
5b377eaf83 Stop ProjectRootActivity from opening caller-supplied project paths
ProjectRootActivity was exported and resolveProjectDef deserialized a caller-supplied ProjectDef whose free-form path was used verbatim for all project file I/O, letting any installed app root the editor at an attacker-controlled directory (confused deputy, CWE-926/CWE-22). Resolve the target project by name against on-disk projects only, drop the serialized EXTRA_PROJECT path extra, and mark the activity exported=false; it is only launched in-process via explicit intents and PendingIntents from widgets and shortcuts.
2026-06-24 14:14:52 -07:00
Adam Brown
47e5f9bf95
Fix flaky editor instrumented tests by re-injecting until the edit lands (#632)
The markdown/scene editor consumes hardware key events and reports edits
through an async flow. sendStringSync could fire before the field had focus
(or before the editor's editOperations collector started), silently dropping
the keystrokes so the observed change never happened and waitUntil timed out.
A longer timeout can't recover dropped input.

typeIntoEditor now re-focuses and re-injects until a caller-supplied
propagated() lambda confirms the change, or a deadline throws a clear error.
NotesWorkflowTest and SceneEditorWorkflowTest pass their existing change
signal and drop their redundant waitUntil blocks.
2026-06-22 11:43:00 -07:00
Adam Brown
85c7913eba Strip dep info blob from fdroid builds 2026-06-22 02:08:29 -07:00
Adam Brown
b44c315431
Target Android API 37 (Android 17)
Bump compileSdk/targetSdk to 37 and AGP to 9.1.1 (9.0.x maxes out at
API 36.1 and can't resolve the minor-versioned android-37.0 platform).

Handle the two breaking behavior changes for API 37 targets:

- Cleartext traffic: usesCleartextTraffic is now ignored without a
  network security config. Self-hosted servers can be plain HTTP
  (ServerSettings.ssl = false), so add network_security_config.xml
  permitting cleartext and reference it from both manifests.

- Local network access: declare ACCESS_LOCAL_NETWORK and request it at
  runtime via a new expect/actual RequestLocalNetworkPermission,
  triggered when the server-setup dialog opens so the grant resolves
  before any LAN connection. No-op on desktop/iOS.
2026-06-07 23:57:01 -07:00
Adam Brown
a2998adcba
Re-enable public-storage projects on F-Droid builds (#568)
Restores the "store projects in public storage" feature, gated to F-Droid builds (the required MANAGE_EXTERNAL_STORAGE permission is disallowed on Google Play).

- Expose the build channel at runtime via BuildConfig.FDROID in the common module.
- Declare the storage permissions only in src/fdroid/AndroidManifest.xml, swapped in for F-Droid builds.
- Restore the storage-location toggle + file-access UI, gated on BuildConfig.FDROID; reconcile the toggle with the real location on open.
- Build the GitHub release APK as the F-Droid flavor.
- Extract the directory move into a tested FileSystem.moveDirectory() helper (fixes the same-path data-loss crash; runs off the UI thread).
- Read the fdroid flag consistently across settings.gradle.kts and module scripts.
- Document the F-Droid build flag in DEVELOPMENT.md.
2026-06-07 19:16:42 -07:00
Adam Brown
eca76803fa
Fix flaky NotesWorkflowTest create-note race (#562)
* Fix flaky NotesWorkflowTest create-note race

createNoteThenOpenIt clicked the create confirm button immediately after
typeIntoEditor. The markdown editor reports text changes through an async
editOperations flow that waitForIdle() doesn't await, so noteText could
still be empty at confirm time -> createNote("") returns NoteError.EMPTY,
the create screen never dismisses, and the browse grid's note-card- nodes
never reappear -> 10s timeout.

Wait for the word/char counter to reflect the typed body before confirming.
Tag the counter (NOTES_CREATE_META_TAG) and add a textOf() harness helper to
read it. Verified on emulator (4/4 runs green).

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

* Guard project list load against a concurrently-deleted project

loadProjectList lists project dirs then reads each one's metadata in a
parallelMap. If a project is deleted between the listing and the read
(another window, or a refresh racing a delete), loadMetadata's recovery
path tries to recreate project.toml in a directory that no longer exists
and throws FileNotFoundException from inside its own catch block, failing
the entire list load (and crashing the instrumented test that exposed it).

Catch the per-project load failure and skip the vanished project - this
also makes the previously-dead `if (metadata != null)` branch live, which
was the original intent.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 16:50:47 -07:00
Adam Brown
d1e265a44c
Add Foundation primitives tier; rename Id/Sync/Settings repos (#558)
* Rename IdRepository to IdAllocator
It is a special foundational primative

* Rename SyncDataRepository to SyncJournal
It is a special foundational primative

* Rename GlobalSettingsRepository to GlobalSettingsStore
It is a special foundational primitive

* docs: add Foundation primitives tier to the architecture doc

Document IdAllocator, SyncJournal, and GlobalSettingsStore as a fixed set of
stateful, cross-cutting primitives that the whole data layer may depend on —
acyclic leaves (GlobalSettingsStore <- SyncJournal <- IdAllocator) that named
the dependency reality instead of treating it as a no-sibling violation.
2026-06-06 01:17:13 -07:00
Adam Brown
04a9772d03
Expand Android instrumented UI test coverage across editor screens (#553)
Add a reusable EditorTestHarness (seed a project via Koin, launch straight
into ProjectRootActivity, navigate, tear down without racing the scope flush)
and one happy-path instrumented test per feature area: navigation smoke,
scene list/editor, notes, encyclopedia, timeline, project home, and global
search.

To make the UI addressable, add optional testTag params to shared design-system
components (FormField, MarkdownEditField, HdHairlineField/TagField/SearchField/
TypePicker, HdBottomBar/HdNavRail) and colocated testTag consts on the relevant
screens. The custom text editor consumes key events rather than Compose SetText
semantics, so the harness types into it via injected keystrokes.

All 9 new tests pass on an api-34 emulator.
2026-06-04 00:20:52 -07:00
Adam Brown
da516e1772
Android e2e UI test harness (on-device project lifecycle) (#516)
ProjectLifecycleTest launches the real ProjectSelectActivity, creates a project,
waits for it to appear in the list, and confirms opening it launches
ProjectRootActivity (via ActivityMonitor). Cleans up in @After.

- Wire jetbrains-compose ui-test-junit4 into the androidTest source set.
- Tag the create-project affordance (CreateProjectButtonTestTag); "Create
  Project" otherwise appears as three separate on-screen texts.
- Run the instrumented suite on an emulator in CI (android-emulator-runner) with
  AVD snapshot caching.
- Convert HashTest from JUnit Jupiter to JUnit4 so the AndroidJUnit4 runner can
  execute it on-device (it had "no runnable methods" otherwise); use assertEquals
  since assert() is a no-op when assertions are disabled on a device. It now
  verifies EntityHasher's golden vector on Android ART.
- Fix a scope-close crash: getSceneBufferDirectory used a non-recursive
  createDirectory, so closing a project whose scenes/ dir is absent threw on the
  teardown path and crashed the process. Use createDirectories (matching its
  siblings) and order the test teardown so it doesn't delete the project mid-close.
2026-06-03 22:11:14 -07:00
Adam Brown
73185ee7b2
Compile the Android instrumented tests in CI (#518)
buildDebug never touched the androidTest source set, so a stale instrumented
test could - and did - rot unnoticed: HashTest stopped compiling after `tags`
was added to EntityHasher.hashNote.

- Add a :android:assembleDebugAndroidTest step: a fast, emulator-free gate that
  compiles + packages the androidTest source set on every PR.
- Fix the stale HashTest to match the current hashNote signature.
- Exclude the duplicate META-INF/LICENSE files the test deps ship so the
  androidTest APK packages.
2026-05-28 23:07:18 -07:00
Adam Brown
ea17b263d5
Fix IllegalStateException: closed race in FileLogger (#524)
The log-consumer coroutine could write to appendBuffer after another
thread (the logging framework / shutdown) had already closed it, e.g.
during CoroutineScope cancellation, throwing IllegalStateException: closed.

Make the single consumer coroutine the sole owner of the sink: it writes,
flushes, and closes appendBuffer (the latter in a finally block so it runs
on normal channel close and on cancellation alike). close() now just closes
the message channel, which ends the consumer; flush()/close() no longer
touch the sink from foreign threads. As a bonus, queued messages are
drained before the sink closes. Applies to both desktop and android.
2026-05-28 23:06:21 -07:00
Adam Brown
2365b33c87
Fix IllegalStateException: closed race in FileLogger (#519)
The log-consumer coroutine could write to appendBuffer after close()
closed it from another thread (e.g. during scope cancellation on
shutdown), throwing IllegalStateException: closed.

Guard all sink access with a lock and a closed flag, and close the
message channel on close() so the consumer terminates cleanly. Applies
to both the desktop and android FileLogger.
2026-05-28 23:00:23 -07:00
Adam Brown
d9cc7d1b76
New Crowdin updates (#497)
* 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 (Chinese Simplified)

[ci skip]

* New translations strings_account_settings.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_project_select_navigation.xml (French)

[ci skip]

* New translations strings_project_select_navigation.xml (Spanish)

[ci skip]

* New translations strings_project_select_navigation.xml (German)

[ci skip]

* New translations strings_project_select_navigation.xml (Italian)

[ci skip]

* New translations strings_project_select_navigation.xml (Ukrainian)

[ci skip]

* New translations strings_project_select_navigation.xml (Chinese Simplified)

[ci skip]

* New translations strings_project_select_navigation.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_desktop.xml (French)

[ci skip]

* New translations strings_desktop.xml (Spanish)

[ci skip]

* New translations strings_desktop.xml (German)

[ci skip]

* New translations strings_desktop.xml (Italian)

[ci skip]

* New translations strings_desktop.xml (Ukrainian)

[ci skip]

* New translations strings_desktop.xml (Chinese Simplified)

[ci skip]

* New translations strings_desktop.xml (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 (Chinese Simplified)

[ci skip]

* New translations strings_project_home.xml (Portuguese, Brazilian)

[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 (Chinese Simplified)

[ci skip]

* New translations strings_scene_editor.xml (Portuguese, Brazilian)

[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 (Chinese Simplified)

[ci skip]

* New translations strings.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 (Italian)

[ci skip]

* New translations strings_about_app.xml (Ukrainian)

[ci skip]

* New translations strings_about_app.xml (Chinese Simplified)

[ci skip]

* New translations strings_about_app.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_notes.xml (French)

[ci skip]

* New translations strings_notes.xml (Spanish)

[ci skip]

* New translations strings_notes.xml (German)

[ci skip]

* New translations strings_notes.xml (Italian)

[ci skip]

* New translations strings_notes.xml (Ukrainian)

[ci skip]

* New translations strings_notes.xml (Chinese Simplified)

[ci skip]

* New translations strings_notes.xml (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 (Chinese Simplified)

[ci skip]

* New translations strings_project_home.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_timeline.xml (French)

[ci skip]

* New translations strings_timeline.xml (Spanish)

[ci skip]

* New translations strings_timeline.xml (German)

[ci skip]

* New translations strings_timeline.xml (Italian)

[ci skip]

* New translations strings_timeline.xml (Ukrainian)

[ci skip]

* New translations strings_timeline.xml (Chinese Simplified)

[ci skip]

* New translations strings_timeline.xml (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 (Chinese Simplified)

[ci skip]

* New translations messages_en.properties (Portuguese, Brazilian)

[ci skip]
2026-05-27 00:41:14 -07:00
Wavesonics
a3b077824a Add iOS support via Compose Multiplatform
iOS now runs the same shared Compose UI as Android and Desktop. The
existing Decompose component graph (IosRoot, ProjectSelection,
ProjectRoot) is reused; the Swift app shrinks to an AppDelegate, a
Koin bootstrap, and a UIViewControllerRepresentable that hands off to
a Kotlin ComposeUIViewController. The SwiftUI starter under
/ios/ios/ui and /ios/ios/DecomposeHelpers is deleted.

Hammer.framework moves from :common to :composeUi so the framework
ships the Compose entry point; Xcode's Run Script invokes
:composeUi:embedAndSignAppleFrameworkForXcode. iosArm64 and
iosSimulatorArm64 targets are declared with libbacktrace source-info
so K/N crashes report file:line. 17 expects across composeUi get iOS
actuals (file pickers via filekit, image loading via Coil3,
LanguageUtil via NSLocale.preferredLanguages, etc).

The adaptive nav scaffolds — bottom bar at compact width, side nav
rail otherwise — are extracted from the Android activities into
shared ProjectSelectScaffold and ProjectRootScaffold in
composeUi/commonMain, along with their Modifier helpers and the
close-confirm dialogs. Android activities are now thin shells that
delegate to these; iOS calls the same scaffolds, so phone/tablet
layout is unified across all three platforms.

App icon and CFBundleDisplayName set so the home screen reads
"Hammer" with the brand logo instead of the blueprint placeholder.
2026-05-20 00:36:29 -07:00
Adam Brown
42f230694f
Add launch-time update notification with release-notes dialog
Lifts version check into VersionCheckRepository + GithubVersionCheckDataSource.
On launch, shows an update dialog when a newer release is available and the
tag hasn't been dismissed (tracked in GlobalSettings). Falls back to the
annotated tag message when the release body is empty. AboutApp's VersionCard
gains a button to open the same dialog on demand.
2026-05-19 23:29:39 -07:00
Wavesonics
7664c1fe8d Upgrade to Gradle and AGP 9 2026-05-18 18:48:39 -07:00
Adam Brown
bbfc27efa2
Track last-edited scene per project and surface in stats
Adds `created` and `lastEdited` timestamps to SceneMetadata, stamped
on every autosave tick in SceneEditorRepository.recordSceneActivity.
StatisticsService rolls them up into a new "Last Edited" tile on the
project home dashboard, and the Android widget reads the same field
from the stats cache.

The timestamps ride the sync wire on ApiProjectEntity.SceneEntity and
participate in EntityHasher.hashScene. On download, local values are
preserved when the server didn't ship them. No conflict resolution —
last write wins.

EntityHasher hash functions had their defaults removed so call sites
can't silently miss a newly added field; this caught a latent bug in
the second markForSynchronization hash call that was relying on
defaults for archived/confirmedReferences/dismissedReferences.
2026-05-17 22:57:10 -07:00
Adam Brown
3f9c497657
Add recent-projects launcher menus for Linux (D-Bus quicklist) and macOS (Dock menu), reaching parity with the Windows jump list. All three platforms are now unified behind a QuickShortcuts interface so callers don't care which OS they're on. Pulled the shared "top N projects by lastAccessed" pipeline up onto ProjectsRepository.getRecentProjects() (used by the desktop and Android shortcut managers), and replaced scattered os.name string-matching with a typed HostOs enum. 2026-05-17 14:40:56 -07:00
Adam Brown
95361e7761
Fix android build 2026-05-17 13:45:19 -07:00
Adam Brown
4b3f657084
Add monochrome icon 2026-05-16 21:31:03 -07:00
Adam Brown
aaaaa6f79f
Update libraries metadata 2026-05-14 21:47:43 -07:00
Adam Brown
2701c77523
New Crowdin updates (#475)
* New translations messages_en.properties (German)

* New translations strings_about_app.xml (German)

* New translations strings_account_settings.xml (German)

* New translations strings_drafts.xml (German)

* New translations strings_encyclopedia.xml (German)

* New translations strings_notes.xml (German)

* New translations strings_project_home.xml (German)

* New translations strings_projects_list.xml (German)

* New translations strings_timeline.xml (German)

* New translations strings.xml (German)

* New translations messages_en.properties (German)

* New translations strings_about_app.xml (French)

* New translations strings_about_app.xml (Spanish)

* New translations strings_about_app.xml (German)

* New translations strings_about_app.xml (Italian)

* New translations strings_about_app.xml (Ukrainian)

* New translations strings_about_app.xml (Chinese Simplified)

* New translations strings_about_app.xml (Portuguese, Brazilian)

* New translations strings_drafts.xml (German)

* New translations messages_en.properties (German)

* New translations strings_about_app.xml (French)

* New translations strings_about_app.xml (Spanish)

* New translations strings_about_app.xml (German)

* New translations strings_about_app.xml (Italian)

* New translations strings_about_app.xml (Ukrainian)

* New translations strings_about_app.xml (Chinese Simplified)

* New translations strings_about_app.xml (Portuguese, Brazilian)

* New translations strings_globalsearch.xml (French)

* New translations strings_globalsearch.xml (Spanish)

* New translations strings_globalsearch.xml (German)

* New translations strings_globalsearch.xml (Italian)

* New translations strings_globalsearch.xml (Ukrainian)

* New translations strings_globalsearch.xml (Chinese Simplified)

* New translations strings_globalsearch.xml (Portuguese, Brazilian)

* New translations strings_globalsearch.xml (French)

* New translations strings_globalsearch.xml (Spanish)

* New translations strings_globalsearch.xml (German)

* New translations strings_globalsearch.xml (Italian)

* New translations strings_globalsearch.xml (Ukrainian)

* New translations strings_globalsearch.xml (Chinese Simplified)

* New translations strings_globalsearch.xml (Portuguese, Brazilian)

* New translations strings_project_home.xml (French)

* New translations strings_project_home.xml (Spanish)

* New translations strings_project_home.xml (German)

* New translations strings_project_home.xml (Italian)

* New translations strings_project_home.xml (Ukrainian)

* New translations strings_project_home.xml (Chinese Simplified)

* New translations strings_project_home.xml (Portuguese, Brazilian)

* New translations strings_project_home.xml (French)

* New translations strings_project_home.xml (Spanish)

* New translations strings_project_home.xml (German)

* New translations strings_project_home.xml (Italian)

* New translations strings_project_home.xml (Ukrainian)

* New translations strings_project_home.xml (Chinese Simplified)

* New translations strings_project_home.xml (Portuguese, Brazilian)

* New translations strings_encyclopedia.xml (French)

* New translations strings_scene_editor.xml (French)

* New translations strings_sync.xml (French)

* New translations strings_encyclopedia.xml (Spanish)

* New translations strings_scene_editor.xml (Spanish)

* New translations strings_sync.xml (Spanish)

* New translations strings_encyclopedia.xml (German)

* New translations strings_scene_editor.xml (German)

* New translations strings_sync.xml (German)

* New translations strings_encyclopedia.xml (Italian)

* New translations strings_scene_editor.xml (Italian)

* New translations strings_sync.xml (Italian)

* New translations strings_encyclopedia.xml (Ukrainian)

* New translations strings_scene_editor.xml (Ukrainian)

* New translations strings_sync.xml (Ukrainian)

* New translations strings_encyclopedia.xml (Chinese Simplified)

* New translations strings_scene_editor.xml (Chinese Simplified)

* New translations strings_sync.xml (Chinese Simplified)

* New translations strings_encyclopedia.xml (Portuguese, Brazilian)

* New translations strings_scene_editor.xml (Portuguese, Brazilian)

* New translations strings_sync.xml (Portuguese, Brazilian)

* 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 (Chinese Simplified)

[ci skip]

* New translations strings_project_home.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 (Chinese Simplified)

[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 (Chinese Simplified)

[ci skip]

* New translations strings_scene_editor.xml (Chinese Simplified)

[ci skip]

* New translations strings_encyclopedia.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_scene_editor.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_notes.xml (French)

[ci skip]

* New translations strings_project_navigation.xml (French)

[ci skip]

* New translations strings_scene_list.xml (French)

[ci skip]

* New translations strings_notes.xml (Spanish)

[ci skip]

* New translations strings_project_navigation.xml (Spanish)

[ci skip]

* New translations strings_scene_list.xml (Spanish)

[ci skip]

* New translations strings_notes.xml (German)

[ci skip]

* New translations strings_project_navigation.xml (German)

[ci skip]

* New translations strings_scene_list.xml (German)

[ci skip]

* New translations strings_notes.xml (Italian)

[ci skip]

* New translations strings_project_navigation.xml (Italian)

[ci skip]

* New translations strings_scene_list.xml (Italian)

[ci skip]

* New translations strings_notes.xml (Ukrainian)

[ci skip]

* New translations strings_project_navigation.xml (Ukrainian)

[ci skip]

* New translations strings_scene_list.xml (Ukrainian)

[ci skip]

* New translations strings_notes.xml (Chinese Simplified)

[ci skip]

* New translations strings_project_navigation.xml (Chinese Simplified)

[ci skip]

* New translations strings_scene_list.xml (Chinese Simplified)

[ci skip]

* New translations strings_notes.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_project_navigation.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_scene_list.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_globalsearch.xml (French)

[ci skip]

* New translations strings_globalsearch.xml (Spanish)

[ci skip]

* New translations strings_globalsearch.xml (German)

[ci skip]

* New translations strings_globalsearch.xml (Italian)

[ci skip]

* New translations strings_globalsearch.xml (Ukrainian)

[ci skip]

* New translations strings_globalsearch.xml (Chinese Simplified)

[ci skip]

* New translations strings_globalsearch.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 (Chinese Simplified)

[ci skip]

* New translations strings_sync.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_notes.xml (French)

[ci skip]

* New translations strings_scene_list.xml (French)

[ci skip]

* New translations strings_notes.xml (Spanish)

[ci skip]

* New translations strings_scene_list.xml (Spanish)

[ci skip]

* New translations strings_notes.xml (German)

[ci skip]

* New translations strings_scene_list.xml (German)

[ci skip]

* New translations strings_notes.xml (Italian)

[ci skip]

* New translations strings_scene_list.xml (Italian)

[ci skip]

* New translations strings_notes.xml (Ukrainian)

[ci skip]

* New translations strings_scene_list.xml (Ukrainian)

[ci skip]

* New translations strings_notes.xml (Chinese Simplified)

[ci skip]

* New translations strings_scene_list.xml (Chinese Simplified)

[ci skip]

* New translations strings_notes.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_scene_list.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_account_settings.xml (French)

[ci skip]

* New translations strings_timeline.xml (French)

[ci skip]

* New translations strings_account_settings.xml (Spanish)

[ci skip]

* New translations strings_timeline.xml (Spanish)

[ci skip]

* New translations strings_account_settings.xml (German)

[ci skip]

* New translations strings_timeline.xml (German)

[ci skip]

* New translations strings_account_settings.xml (Italian)

[ci skip]

* New translations strings_timeline.xml (Italian)

[ci skip]

* New translations strings_account_settings.xml (Ukrainian)

[ci skip]

* New translations strings_timeline.xml (Ukrainian)

[ci skip]

* New translations strings_account_settings.xml (Chinese Simplified)

[ci skip]

* New translations strings_timeline.xml (Chinese Simplified)

[ci skip]

* New translations strings_account_settings.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_timeline.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_account_settings.xml (German)

[ci skip]

* New translations strings_account_settings.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 (Chinese Simplified)

[ci skip]

* New translations strings_sync.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_projects_list.xml (French)

[ci skip]

* New translations strings_projects_list.xml (Spanish)

[ci skip]

* New translations strings_projects_list.xml (German)

[ci skip]

* New translations strings_projects_list.xml (Italian)

[ci skip]

* New translations strings_projects_list.xml (Ukrainian)

[ci skip]

* New translations strings_projects_list.xml (Chinese Simplified)

[ci skip]

* New translations strings_projects_list.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_globalsearch.xml (French)

[ci skip]

* New translations strings_globalsearch.xml (Spanish)

[ci skip]

* New translations strings_globalsearch.xml (German)

[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_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_project_navigation.xml (French)

[ci skip]

* New translations strings_project_navigation.xml (Spanish)

[ci skip]

* New translations strings_project_navigation.xml (German)

[ci skip]

* New translations strings_project_navigation.xml (Italian)

[ci skip]

* New translations strings.xml (French)

[ci skip]

* New translations strings_project_select_navigation.xml (French)

[ci skip]

* New translations strings.xml (French)

[ci skip]

* New translations strings.xml (Spanish)

[ci skip]

* New translations strings_project_select_navigation.xml (Spanish)

[ci skip]

* New translations strings.xml (Spanish)

[ci skip]

* New translations strings.xml (German)

[ci skip]

* New translations strings_project_select_navigation.xml (German)

[ci skip]

* New translations strings.xml (German)

[ci skip]

* New translations strings.xml (Italian)

[ci skip]

* New translations strings_project_select_navigation.xml (Italian)

[ci skip]

* New translations strings.xml (Italian)

[ci skip]

* New translations strings_globalsearch.xml (Italian)

[ci skip]

* New translations strings_globalsearch.xml (Ukrainian)

[ci skip]

* New translations strings_globalsearch.xml (Chinese Simplified)

[ci skip]

* New translations strings_globalsearch.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_project_home.xml (Ukrainian)

[ci skip]

* New translations strings_project_home.xml (Chinese Simplified)

[ci skip]

* New translations strings_project_home.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_scene_editor.xml (Ukrainian)

[ci skip]

* New translations strings_scene_editor.xml (Chinese Simplified)

[ci skip]

* New translations strings_scene_editor.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_project_navigation.xml (Ukrainian)

[ci skip]

* New translations strings_project_navigation.xml (Chinese Simplified)

[ci skip]

* New translations strings_project_navigation.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings.xml (Ukrainian)

[ci skip]

* New translations strings_project_select_navigation.xml (Ukrainian)

[ci skip]

* New translations strings.xml (Ukrainian)

[ci skip]

* New translations strings.xml (Chinese Simplified)

[ci skip]

* New translations strings_project_select_navigation.xml (Chinese Simplified)

[ci skip]

* New translations strings.xml (Chinese Simplified)

[ci skip]

* New translations strings.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings_project_select_navigation.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings.xml (Portuguese, Brazilian)

[ci skip]
2026-05-14 21:28:32 -07:00
Adam Brown
ff7fb7cce3
Fix android build 2026-05-14 21:11:23 -07:00
Wavesonics
e76be9c8a4 Updating logo and icons 2026-05-13 21:53:57 -07:00
Adam Brown
1963c09632
Apply new navigation design to android 2026-05-13 00:08:14 -07:00
Adam Brown
b1eff6a632
Expandable nav rail with sliding indicator 2026-05-12 14:00:55 -07:00
Adam Brown
76883d7c25
Add Story Info home-screen widget
Resizable single-project stats widget (4x1 / 2x2 / 3x2 / 3x3+) showing
total word count, cadence-goal progress, today/streak, a 7-day sparkline,
and Open/Note actions. Adds a config activity for project selection.

Bumps Glance to 1.2.0-rc01 and wires up providePreview + setWidgetPreviews
so all three widgets render previews on Android 15+ pickers. Adds the
project name to the Add Note widget header. Extracts parseDailyWordTotals
and formatWidgetWords as shared helpers.
2026-05-12 12:03:44 -07:00
Adam Brown
03e5643bb4
Add dynamic launcher shortcuts for recent projects 2026-05-12 01:08:31 -07:00
Adam Brown
6ea8c5eeb5
Add Stories List home-screen widget
Lists projects with cached word counts; row tap opens the project, header opens the project picker. Lifts shared mono text styles into WidgetDesignSystem and extracts ProjectRootActivity.createIntent to dedupe the deep-link builder.
2026-05-12 00:34:12 -07:00
Adam Brown
456c38fb59
Restyle Add Note widget with editorial design 2026-05-12 00:11:00 -07:00
Adam Brown
18221fcd42
Redesign project selection 2026-05-09 12:57:34 -07:00