Commit graph

2960 commits

Author SHA1 Message Date
Wavesonics
a1e1f48ff5 More cleanup and dedup of api routes code 2026-05-26 18:51:50 -07:00
Wavesonics
5b8ffdd744 Simplify ProjectRoutes code
Dedup lots of code
2026-05-26 18:32:07 -07:00
Wavesonics
07c238e210 Exclude tests from codacy 2026-05-26 17:52:51 -07:00
Adam Brown
6fcd09f4ba
Upgrade database libs 2026-05-26 00:25:11 -07:00
Adam Brown
912a636c0e
Fix junit 2026-05-26 00:20:59 -07:00
Adam Brown
6851e3a98f
Upgrade UI libs 2026-05-26 00:17:57 -07:00
Adam Brown
9c7105848b
Add support docs 2026-05-25 18:40:04 -07:00
Adam Brown
7f0aa8b1df
Fix timeline A-Z sort and add Z-A parity option
A-Z mode used a plain lexicographic compare, so "Chapter 10" sorted
before "Chapter 2". Switched to a natural-order comparator that
compares digit runs by numeric value, and added a TitleDesc mode so
title sort has the same asc/desc parity as sequence sort.
2026-05-25 12:22:35 -07:00
Adam Brown
2e411cd2b8
Epub:
- File picker now lets you set file name
- Added Table of Contents
- Cleaned up chapter spacing
2026-05-24 11:03:42 -07:00
Adam Brown
3ed57a5986
Style the epub export better 2026-05-24 10:45:58 -07:00
Adam Brown
b2871c188c
Fix iOS external file module 2026-05-24 10:30:49 -07:00
Adam Brown
88ffe142d5
Add epub export format
Also refactor the export system, much cleaner now
2026-05-24 10:22:05 -07:00
Adam Brown
ef86535b64
Route loadSceneMetadata through StrRes to fix headless CI
The static getString() call in loadSceneMetadata reaches
Toolkit.getScreenResolution() on Compose Desktop, which throws
HeadlessException on Linux CI and broke 8/9 integration tests.
RoundTripTestBase already stubbed StrRes for this exact reason;
this call was the one that bypassed it.
2026-05-24 01:27:06 -07:00
Adam Brown
77e553cb3b
Polish the prepareForRelease dialog
- Switch to FlatLaf (Darcula) for an IntelliJ-grade look and feel.
  Lives in buildSrc only — never ships in the released app.
- Restructure the dialog into TitledBorder sections: Version,
  Publish scope, Will push tag, Changelog.
- Replace the always-on checkbox grid with an All / Targeted scope
  toggle. Full release (the common case) is now one click and shows
  the bare vX.Y.Z tag; Targeted enables the per-store checkboxes.
- Make the computed tag the dialog's focal point — large monospaced
  bold, italic + dimmed when no platforms are picked.
- Right-align Commit button, live-update window title to show the
  version transition, bump frame size for more changelog room.
2026-05-24 01:17:19 -07:00
Adam Brown
dbd0b4d013
Added the ability to publish to a single store
We've run into many problems where everything publishes fine, except 1 store. This provides a way to unblock that one store without pushing to the rest.
2026-05-24 00:51:40 -07:00
Adam Brown
b9d5ed68f0
Update roadmap 2026-05-24 00:19:16 -07:00
Adam Brown
5a08059c94
Harden extracted publish workflows from code review
- Grant contents: write to the publish-fdroid-tag caller job so the
  reusable workflow's git push tag actually has the token scope it
  needs (reusable workflows can't elevate beyond the caller).
- Add a track input to publish-google-play (internal/alpha/beta/
  production), default internal for manual dispatch so a stray
  workflow_dispatch click can't ship develop straight to Production.
  publish-release.yml explicitly passes track: production.
- Pass release_tag through env vars in publish-snap and
  publish-fdroid-tag instead of interpolating into shell, closing
  the workflow_dispatch command-injection vector.
- Validate fdroid release_tag matches semver before letting it near
  git tag / git push.
2026-05-23 23:18:40 -07:00
Adam Brown
dfaa3a9900
Clarify why SceneTimestampsTest can't pin server timestamps exactly
The previous comment claimed the range assertion proved the edit
"round-tripped instead of being stamped with NOW() server-side." It
doesn't — server-side NOW() during sync falls in the same window.

Replace with an honest description: storeAllBuffers inside prepareForSync
bumps lastEdited right before upload, and the debounced contentFlow can
fire again post-sync, so client-bumped-during-sync and server-stamped-
on-receipt are observationally identical without instrumenting the sync
pipeline. The range check still rules out drops, epoch-0 resets, and
clearly-wrong stamps.
2026-05-23 13:23:59 -07:00
Adam Brown
4d299a0eb9
Extract each store publishing into their own actions
This allows for debugging individual stores, and in the future should allow single store releases
2026-05-23 13:05:52 -07:00
Adam Brown
7f5e65df53
Fix #504
Skia version mismatch, downgrade CMP until its fixed
Also remove alpha picker in theme screen, we dont need it
2026-05-23 02:21:31 -07:00
Adam Brown
8eb23f6f41
Extract MSIX publish into reusable workflow with manual trigger (#506)
Lets us iterate on the Partner Center submission logic via
workflow_dispatch (with a dry_run mode that skips the final commit
and cleans up the draft) instead of cutting a release every time.
Release pipeline keeps the publish-google-play gate by calling the
reusable workflow with secrets: inherit.
2026-05-23 01:01:20 -08:00
Adam Brown
3329ddfb44
Update android screenshots 2026-05-23 01:48:43 -07:00
Adam Brown
b55020953b
Update preview image 2026-05-23 01:43:04 -07:00
Wavesonics
38a4796c5e Update our store asset generation 2026-05-23 01:00:28 -07:00
Adam Brown
803fadd72c
Fix non-mac app store desktop runs 2026-05-22 23:49:53 -07:00
Adam Brown
8f2592ad58
Merge branch 'postgres-server-refactor' into develop
Migrate the sync server's storage from SQLite to PostgreSQL with two
drivers: an in-process Zonky embedded server (the personal-install
experience) and a remote-Postgres mode (for multi-tenant deployments).
Single dialect, single set of .sq files, single set of DAOs.

The v1 schema bakes in Tier 1+2 cleanup: CITEXT for case-insensitive
columns, UUID for project ids, TIMESTAMPTZ everywhere, real BOOLEANs,
BIGSERIAL keys, partial indexes for sparse columns, and explicit ON
DELETE behavior plus CHECK constraints on every table.

A one-shot SQLite-to-Postgres migrator runs at startup against an
existing ~/hammer_data/server.db, copies it row-by-row in a single
transaction, verifies row counts in-transaction, then renames the
source to server.db.migrated-<ts>.bak. --migrate-dry-run lets operators
rehearse against a copy of production.

Verified:
- :server:test 409/0/0/0 (~2 min, embedded Postgres shared across the
  JVM with truncate-between-tests)
- :integrationTests:jvmTest 9/0/0/0 (~54s)
- :desktopTest green via pre-commit hook
- Embedded-mode boot smoke against a fresh ~/hammer_data
- Remote-mode boot smoke against a Postgres 16 container, schema landed
  cleanly with CITEXT/UUID/TIMESTAMPTZ promotions intact.

Notable carry-overs documented for ops:
- auth_token PK changed from `token` to (user_id, install_id); the
  migrator collapses any historical duplicates via ON CONFLICT.
- One-shot migration is irreversible; an off-box backup of server.db
  is the operator's job, not a replacement for the in-process .bak.
- 120MB of embedded Postgres binaries are now bundled in the server
  jar (one of five platform artifacts is extracted at boot).
- Pre-commit hook now skips :server:test; CI should still run it.
2026-05-22 23:30:23 -07:00
Adam Brown
7f2dea41a5
Remove flatpakGradleGenerator
We only needed this for flathub which we gave up on
2026-05-22 23:30:04 -07:00
Adam Brown
976d67926a
Fix integration tests against the Postgres backend
Two failures came up running :integrationTests:jvmTest after the
SQLite-to-Postgres migration.

1. RoundTripTestBase seeded server_config with a raw epoch integer:

       INSERT INTO server_config VALUES ('whitelist_enabled', 'false', 1704067200);

   server_config.updated_at is now TIMESTAMPTZ; Postgres refused to
   coerce the integer. Wrapped the value in to_timestamp(...).

2. SceneTimestampsTest's upload test asserted exact equality between a
   pre-sync local lastEdited snapshot and the server's stored value.
   SceneEditorRepository runs a debounced (~500ms) auto-save that calls
   recordSceneActivity → bumps lastEdited on every Editor-source store,
   and the storeAllBuffers pass inside sync's prepareForSync triggers
   another. The exact-equality assertion happened to pass against fast
   in-memory SQLite because the whole test finished inside the debounce
   window; with the slower embedded Postgres it doesn't.

   The test's intent is "edit timestamps round-trip to the server, not
   stamped with NOW() server-side." Reframed the assertion as "server's
   created and lastEdited both fall inside [beforeEdit, afterSync]" —
   which is what the test actually proves.
2026-05-22 23:18:25 -07:00
Adam Brown
4cef35195f
Truncate user tables between tests instead of dropping the schema
Previously each test reset state by dropping and recreating the public
schema, which forced PostgresSchemaInitializer to re-run on every test:
CREATE EXTENSION citext, ServerDatabase.Schema.create(), ALTER COLUMN
... TYPE CITEXT/UUID for half a dozen columns, and the version-table
roundtrip. With ~400 tests that's a lot of DDL.

Now the schema is built once for the JVM (lazy in SharedTestPostgres)
and each test just does

    TRUNCATE TABLE <all-user-tables> RESTART IDENTITY CASCADE

which drops to a single statement against pg_tables-discovered tables,
with sequences reset and FK chains cascaded.

Server test suite drops from ~4 minutes to ~2 minutes (409/0/0/0).

Also drop the now-dead createSchema and enforceForeignKeys constructor
parameters; nobody passed false to either.
2026-05-22 23:00:51 -07:00
Adam Brown
79c1aa5a24
Address migrator review follow-ups
- Replace dead MigrationParityChecker.check()/checkProject() stub with
  checkAllCounts(copied, conn) that compares the copy phase's per-table
  row counts against SELECT COUNT(*) for every table in-transaction. All
  12 tables now covered, no SQLite re-reads.
- canonicalizeUuid now takes (table, pk, text) so malformed-UUID aborts
  name the offending row in the error message.
- EmbeddedPostgresDatabase.initialize() stores the shutdown hook in a
  field, refuses double-init, and close() removes the hook before
  tearing the pool down — no more leaked hooks across re-init.
- Tighten narration-style comments in the migrator, the Postgres test
  database fixture, and project.sq.
2026-05-22 22:55:33 -07:00
Wavesonics
2517bb80c6 Add manifest-driven asset generation pipeline
Replace ad-hoc icon scripts with a single source-of-truth manifest
  (scripts/assets.yaml) and a Python generator that renders every app icon,
  store-listing graphic, MSIX tile, favicon, and the Play feature graphic from
  two SVG sources. Compositions (icon + "Hammer" wordmark in Kingthings
  Trypewriter) are defined once as percentages of canvas and reused across the
  Play feature graphic, MSIX wide/splash tiles, and the Snap featured banner.

  Adds the missing snap store icon, fixes the snap desktop icon to the correct
  256x256 hicolor size, and routes uploads-only assets to build/store-assets/
  (gitignored).

  See docs/ASSET-GENERATION.md for the manifest schema.
2026-05-22 19:26:49 -07:00
Adam Brown
f2c3df8d2b
Tighten migrator + migration tests
Code-review pass after the migration feature landed. Behavior unchanged;
all 409 server tests still green.

Migrator (SqliteToPostgresMigrator):
  * Extract a single `copyTable(conn, sql, rows, bind)` helper. Every
    per-table routine collapses to ~10 lines of binding logic, and the
    BATCH_SIZE flush that only `copyStoryEntity` used now applies to
    every table for free.
  * Kill two N+1s in `copyAuthToken` and `copyDeletedProject` — both
    were iterating every account and calling `getTokensByUserId` /
    `getDeletedProjects` per row. Add `getAllForMigration` queries to
    AuthToken.sq and DeletedProject.sq (matching every other legacy
    table) and use them.
  * Hoist `parseLegacyTimestamp` into `utilities/DateTimeUtils.kt`; the
    parity checker now calls the shared one too.
  * Drop the dead `@Suppress("unused")` import-pin properties and the
    `kotlin.time.Clock` / `EmbeddedPostgresConfig` imports they pinned.
  * Rename `countAnyTable` → `countAccountRows` (it only ever counted
    one table; the name was lying about what it did).
  * Drop the unnecessary `originalAutoCommit` save/restore — Hikari
    resets every checked-out connection.
  * Trim verbose narration comments throughout.

Migration tests:
  * Drop the `dataDirName` parameter from `MigrationTestSupport.storageFor` —
    it was always derived from `port` and never overridden by any caller.
  * Add `MigrationFixtureBuilder.withSchema(dbPath, seed)` and use it
    from both the smoke test and the malformed-UUID failure-mode test,
    replacing the duplicated open-driver/Schema.create dance.
  * Rewrite `SqliteToPostgresMigratorSmokeTest` to use
    `MigrationTestSupport` + `MigrationFixtureBuilder.tiny` (was
    inlining its own port allocator and seed helper, drifting from the
    rest of the suite).
2026-05-21 02:15:13 -07:00
Adam Brown
519b8a8542
Document PostgreSQL storage and the SQLite-to-Postgres migration 2026-05-21 02:00:52 -07:00
Adam Brown
f55009ae34
Skip slow server tests in the pre-commit hook
The server test suite now runs against embedded PostgreSQL and takes
~10 minutes per run — too slow to gate every commit. The hook now only
runs `desktopTest`. Run `:server:test` manually before pushing, or rely
on CI to catch server-side regressions.
2026-05-21 01:58:06 -07:00
Adam Brown
bbee6fb14b
Build out the migration test suite
Adds a fixture builder + supporting scaffolding + three test classes that
exercise the migrator end-to-end against real embedded Postgres:

  * MigrationFixtureBuilder.tiny(path) seeds a v5 SQLite database with
    every table populated — 2 accounts (admin + community author with
    pen_name and bio), 1 project with 4 story_entities (one per type,
    one encrypted), a deleted_entity, a project_data sync blob, public
    + password-protected project_access, a deleted_project, a live and
    an expired auth_token, an unused password_reset_token, a white_list
    row, a server_config row, a writing_activity row.

  * MigrationTestSupport centralises the boilerplate: per-test port
    allocation, `user.home` redirection so getRootDataDirectory() lands
    under @TempDir, embedded-Postgres open/close.

  * MigrationFullTableParityTest runs the migrator against the tiny
    fixture and asserts row counts plus a spot-check on a representative
    column per table — including UUID round-trip on project.uuid, the
    `community_member` flag, encrypted vs unencrypted story entities,
    and that the BIGSERIAL sequence advanced past max(id) so the next
    auto-generated account id is 3. Also verifies CITEXT promotion took
    effect by reading information_schema and confirming a case-shifted
    `findAccount(EMAIL)` matches a lowercase row.

  * MigrationFailureModesTest covers the three abort conditions:
    Postgres pre-seeded → refuses cleanly, sqlite untouched; malformed
    UUID → transaction rollback, Postgres empty, sqlite untouched;
    dry-run → reports success but does not commit or rename.

  * MigrationIdempotencyTest covers the two no-op conditions: no
    `server.db` present, and `.bak` already present.
2026-05-21 01:57:55 -07:00
Adam Brown
a273e22444
Fix CITEXT comparison silently falling back to case-sensitive
The CITEXT promotion in PostgresSchemaInitializer makes the column store
case-insensitively, but every production query that filtered on email,
pen_name, or project.name was silently still doing a case-sensitive
match. With LHS = citext and a bound `String` parameter sent as `text`
(or `varchar`), Postgres picks the `text = text` operator (via the
implicit `citext → text` cast) and the case-insensitive comparison is
lost.

Fix: cast every bound parameter against a CITEXT column to CITEXT
explicitly — `WHERE email = CAST(? AS CITEXT)` — so Postgres binds the
parameter to the `citext = citext` operator. Same shape as the existing
`CAST(? AS UUID)` plumbing for the uuid columns.

Also promotes `project.name` to CITEXT to match the SQLite-era
`LOWER(name) = LOWER(?)` semantics in `ProjectAccess.sq` (find-by-URL-
slug, the community feed). As a side effect, the `UNIQUE(name, user_id)`
constraint becomes case-insensitive too, which closes a latent gap where
two same-name-different-case projects could coexist but only one would
be findable.

Affected queries:
  * Account.sq: findAccount, findAccountByPenName, isPenNameTaken
  * WhiteList.sq: isWhiteListed, addToWhiteList, removeFromWhiteList,
    updateReason
  * ProjectAccess.sq: findPublicProjectByPenNameAndProjectName,
    findProjectByPenNameProjectNameAndPassword, hasAnyAccessForProject,
    getPublishedStoriesByPenName
  * Project.sq: updateSyncData, findProjectByName
2026-05-21 01:57:32 -07:00
Adam Brown
2d3fec3d2c
Complete per-table coverage in SqliteToPostgresMigrator
The migrator now fully streams every legacy SQLite table into PostgreSQL.
Previously password_reset_token, project, project_data, project_access,
story_entity, deleted_entity, server_config, and writing_activity were
stubs that silently returned 0 rows — a real production server.db would
have migrated successfully but landed in Postgres with most tables empty.

Implementation:
  * Adds a `getAllForMigration: SELECT * FROM <table>` query to each
    legacy `.sq` that lacked a getAll equivalent. These queries are
    referenced only by the migrator and disappear when the legacy
    SqlDelight tree is removed in a future release.
  * auth_token uses `ON CONFLICT (user_id, install_id) DO UPDATE` to
    handle the rare case where legacy data has multiple tokens for the
    same install (the legacy PK was `token`, the new PK is the pair).
  * story_entity streams in batches of 500 to bound peak memory on the
    high-volume table.
  * resetSequences switches to the 3-arg `setval(seq, value, is_called)`
    form so empty tables get a sequence starting at 1, not 2.
2026-05-21 01:57:00 -07:00
Adam Brown
1a0a175f65
Promote project.uuid and deleted_project.uuid to UUID type
The two `uuid` columns are now native PostgreSQL `UUID` (16-byte storage +
format validation) instead of `TEXT`. `PostgresSchemaInitializer` runs
`ALTER TABLE … TYPE UUID USING uuid::uuid` after `Schema.create()` for
both `project` and `deleted_project`.

Postgres won't auto-cast bound text parameters against a `UUID` column in
comparisons or against the UNIQUE index, so every WHERE/INSERT in
`Project.sq` and `DeletedProject.sq` that binds the column now goes through
`CAST(:uuid AS UUID)`.

Database-backed test fixtures that previously used placeholder strings like
`"Test UUID"` or `"project-id-1"` now use real canonical UUIDs:
  * `ProjectEntityDatabaseDatasourceTest`
  * `ServerProjectDataRepositoryTest`
  * `ServerWritingActivityRepositoryTest`
  * `ProjectsDatabaseDatasourceTest`
Mock-only tests retain their placeholder strings — those never reach
Postgres.

Test harness changes (necessary to make the full suite stable on Windows):
  * `SqliteTestDatabase` now shares a single process-wide Zonky embedded
    Postgres across every test. Each instance's `initialize()` drops and
    recreates the `public` schema for isolation, then runs the production
    `PostgresSchemaInitializer`. Spinning up a fresh embedded Postgres per
    test method previously accumulated hundreds of postgres child processes
    and hit Windows handle limits.
  * `SqliteTestDatabase.initialize()` is now idempotent within an instance.
    The production `Database` is initialized a second time by
    `configureDependencyInjection` during Ktor startup, and that second call
    must not wipe the fixture data the test just loaded.
2026-05-21 01:08:48 -07:00
Adam Brown
cb27ca71b0
Adapt server test harness to embedded PostgreSQL
`SqliteTestDatabase` now boots a per-instance Zonky embedded Postgres
(random free port) and runs the production `PostgresSchemaInitializer` so
test databases mirror the prod schema exactly. The class name is preserved
to minimize churn in dependent tests; the implementation is what changed.

Every test-side JDBC connection has `session_replication_role = replica`
applied on checkout, mirroring the old SQLite `foreign_keys=false` setting
that existing tests rely on for out-of-order fixture inserts (e.g.
`addDeletedProject` before the corresponding account is created). The
production `ProjectEntityDatabaseDatasource.deleteProject` now scrubs
`story_entity` rows explicitly rather than relying on FK CASCADE so behavior
is identical with FK enforcement on or off.

New `LegacyFixtureSqlTranslator` rewrites the existing
`src/test/resources/*/server.db.sql` fixtures on the fly:
  * `INSERT INTO account`: 0/1 → FALSE/TRUE for the boolean columns.
  * `INSERT INTO white_list` / `server_config`: epoch-seconds INTEGER →
    `to_timestamp(N)` for the now-TIMESTAMPTZ columns.
  * `INSERT INTO story_entity`: appends a NULL `cipher` value when the
    fixture predates that column.

`FileResourcesUtils.setupDatabase` runs every statement through the
translator and resyncs each BIGSERIAL `id` sequence with `setval(...)` so
subsequent auto-id inserts don't collide with the explicit ids the fixtures
load.

`SqliteToPostgresMigratorSmokeTest` exercises the one-shot migrator end to
end against a real legacy SQLite file and a real embedded Postgres: seeds a
v5 SQLite database via `LegacySqliteDatabase.Schema`, drives the migrator
under a redirected `user.home`, and asserts row counts and the
`server.db.migrated-*.bak` rename.

Test files that previously passed SQLite-formatted date strings now build
`kotlin.time.Instant` directly. `DataMigratorTest` is removed — the SQLite
schema-migration path it exercised no longer exists at runtime; the
SQLite-to-Postgres data migration has its own dedicated test.
2026-05-21 00:28:46 -07:00
Adam Brown
d22b6f9120
Migrate server storage from SQLite to PostgreSQL
The server now persists its sync state to PostgreSQL, with two interchangeable
drivers selected by `storage.type` in the TOML config:

  * EMBEDDED (default): in-process Zonky Postgres on a pinned port, data dir
    `~/hammer_data/pgdata`. Preserves the "single JAR, no external services"
    feel of the old SQLite setup for personal installs.
  * REMOTE: HikariCP-pooled JDBC to an externally-managed Postgres for
    multi-tenant deployments.

Schema is laid in at v1 with Tier 1 + Tier 2 features baked in:
  * TIMESTAMPTZ AS Instant for every timestamp column (via
    `InstantColumnAdapter`).
  * BOOLEAN columns (was `INTEGER AS kotlin.Boolean`).
  * BIGSERIAL primary keys.
  * CITEXT extension promoted onto `account.email`, `account.pen_name`, and
    `white_list.email` after `Schema.create()` for case-insensitive UNIQUE
    indexes.
  * Explicit `ON DELETE` per FK; partial UNIQUE index on `account.pen_name`;
    partial index for community-author lookup; CHECK constraints for length
    and `expires > created` invariants.
  * `INSERT ... ON CONFLICT DO UPDATE` for upserts; insert-then-lookup
    pattern replaces `last_insert_rowid()` triple-queries.

UUID promotion for `project.uuid` and `deleted_project.uuid` is deferred to a
follow-up release; existing tests pass placeholder strings that fail UUID
validation.

The legacy SQLite SqlDelight setup moves to
`src/main/sqldelight-legacy-sqlite/.../legacy/` and stays compiled as a
read-only `LegacySqliteDatabase` so the one-time migrator can stream rows
from it.

`SqliteToPostgresMigrator` runs at startup when a legacy `server.db` is
present alongside a Postgres-configured server: copies row-for-row inside a
single Postgres transaction with `session_replication_role = replica` to
suppress FK checks during the bulk insert, runs a row-count parity check on
the same connection, commits, and renames the source file to
`server.db.migrated-<timestamp>.bak`. A `--migrate-dry-run` CLI flag runs
the same flow but rolls back, for staging rehearsal.

Downstream consumers (DAOs, repositories, frontend pages) updated to use
`kotlin.time.Instant` directly instead of SQLite-flavored date strings.
2026-05-21 00:28:06 -07:00
Wavesonics
0700d0c5e5 Wire up iOS App Store publishing via Fastlane (local + CI)
Mirror the Mac setup with an :ios platform block exposing ios_testflight
and ios_release lanes, plus a publish-ios-app-store job in the release
workflow. Both lanes reuse the existing App Store Connect API key (the
.p8 isn't platform-specific). The CI job installs the provisioning
profile to both Xcode 16's path and the legacy Fastlane location for
compatibility.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 00:18:16 -07:00
Wavesonics
246914d10f Cleanup plist 2026-05-20 23:27:12 -07:00
Wavesonics
331ff2336c Prep iOS Info.plist + profile reference for App Store submission
Declare ITSAppUsesNonExemptEncryption=false so App Store Connect stops
asking on every upload, drop the legacy armv7 UIRequiredDeviceCapabilities
entry, and align CFBundleShortVersionString with the shared codebase
version (3.0.3) in gradle/libs.versions.toml. pbxproj now references the
freshly-issued "Hammer AppStore iOS" provisioning profile.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:16:31 -07:00
Wavesonics
496e05d934 clean up ios user files 2026-05-20 22:45:14 -07:00
Wavesonics
1253b74bf5 Bump Gradle JVM args for K/N build 2026-05-20 22:24:03 -07:00
Wavesonics
2ef911c1b4 Fix iOS crash from stale projectsDirectory across sandbox UUID changes
iOS sandbox container UUIDs can change between installs, dangling the
absolute projectsDirectory persisted by a prior install. Decorate the
iOS GlobalSettingsDatasource to re-anchor the post-/Documents/ suffix
under the live Documents dir on load and persist the correction.
2026-05-20 22:18:23 -07:00
Wavesonics
16ca0136f7 Merge branch 'claude/angry-beaver-26ac54' into develop
Restyle scene-editor rename dialog to design-system FormDialog.
2026-05-20 22:13:41 -07:00
Wavesonics
9a1b3a6c57 Merge branch 'claude/sharp-lalande-0c7fef' into develop
# Conflicts:
#	composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/notes/CreateNoteUi.kt
2026-05-20 22:11:58 -07:00
Wavesonics
191b9020cd Extract hard-coded CreateNoteUi labels to strings resource
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:08:21 -07:00
Wavesonics
777d7fb3c3 Restyle scene-editor rename dialog to design-system FormDialog
The Android and iOS EditorTopBar still wrapped the rename input in a
SimpleDialog + TextField + IconButton row, missing the § masthead,
hairline FormField, and Save/Cancel button row used by every other
dialog. Swap both for FormDialog/FormField with a § RENAME marker,
SCENE/GROUP/ROOT meta caption, and the same ProjectsRepository
validation pipeline as SceneRenameDialog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:07:09 -07:00