Commit graph

2960 commits

Author SHA1 Message Date
Adam Brown
6897abbd76 PR1: polymorphic content decryption via cipher registry
Decryption now dispatches on each row's stored cipher tag instead of the
single DI-injected encryptor. Adds ContentEncryptorRegistry and an identity
PlaintextContentEncryptor (tag "none"); loadEntity resolves the row's
encryptor from its cipher column. NULL cipher resolves to plaintext
(pre-#367 rows); an unknown non-null tag fails loudly rather than reading
ciphertext as garbage. Store path is unchanged.

Tests: mixed-tag read (aesgcm/none/NULL), unknown-tag loud failure, and
cipher-orthogonal-to-hash invariant.
2026-06-16 19:52:37 -07:00
Adam Brown
9d52fe61c3 docs: add testing strategy + per-PR test gates for secret storage
Frame tests around the catastrophic failure modes (unreadable data, mid-convergence
loss, silent corruption, false 'safe to delete' signal, auth breakage). Add the
grandfather golden-corpus test (incl. lossy-secret trap), crash/no-loss and
completion-signal gates, over-cap handling, and a convergence dry-run. Move the
plaintext read path into PR1 so NULL/plaintext rows are readable immediately.
2026-06-16 19:37:59 -07:00
Adam Brown
7a1c10c446 docs: keyring CLI uses existing kotlinx-cli parser, no new dependency 2026-06-16 19:24:08 -07:00
Adam Brown
b3300db568 docs: finalize secret-storage decisions; drop background sweep
Record the four settled decisions (NULL=plaintext, single-JSON keyring with
roles, File+Env explicit-selection providers with no auto-generation, blocking
pre-launch convergence). Drop the online background sweep in favor of
offline-only maintenance, add a read-only admin encryption-status view, and note
the design doc will be replaced by an admin tutorial when the feature ships.
2026-06-16 19:18:22 -07:00
Adam Brown
6264c86c2e docs: add server secret storage implementation plan 2026-06-16 18:08:40 -07:00
Claude
1859d53635 docs: add server secret storage & key management design doc
Capture the design discussion for moving the server secret off the
co-located filesystem file toward a pluggable, versioned keyring with
per-row encryption modes (including no-encryption), online key rotation,
and a provable key-retirement signal.
2026-06-16 18:08:40 -07:00
Adam Brown
71fda2e2ee Resolve web project URLs losslessly to fix name-with-dash 404s
formatForUrl maps spaces to dashes and decodeFromUrl maps every dash
back to a space, so a project whose name contains a literal dash (e.g.
a "2026-06-07" date) could not be reversed and its web page 404'd.

Add ProjectsRepository.findProjectByUrlName, which tries the exact
decoded name then matches the URL segment against each project
re-encoded with formatForUrl. formatForUrl output is unchanged, so
existing shared/published links keep working. Route the story and
review lookups through it, and compare slugs in reviewMatchesUrlProject.
2026-06-16 02:34:10 -07:00
Adam Brown
ae8432eea6 Fix Windows CoInitializeEx crash in desktop file dialogs
FileKit's Compose remember*Launcher runs the native picker on a shared
Dispatchers.IO thread and calls CoInitializeEx(STA), which throws
"CoInitializeEx failed" (RPC_E_CHANGED_MODE) when that pooled thread was
already COM-initialized MTA. Intermittent and crashes the app.

Add retryingFileDialog and route every desktop-reachable picker through
FileKit's suspend API instead of the Compose launchers, retrying past the
transient COM failure and returning null rather than crashing. iOS/Android
keep the Compose launchers.
2026-06-16 02:27:23 -07:00
Adam Brown
ed74a11dfc Sync illegal-named server projects down instead of dropping them
A project name the server accepts can contain characters this client
rejects (e.g. #). Such projects failed local creation during account
sync and were silently skipped, so they never appeared on the client
and could not be deleted from it.

Map server names through ProjectsRepository.toLocalSafeName when
creating or renaming local projects from server changes, and log a
failed local create as an error rather than a warning.

Also plug a test leak: SceneEditorRepository{Archive,Other}Test
mockkObject ProjectsRepository.Companion without unmocking, which
globally forced validateFileName to succeed for any later test.
2026-06-16 02:24:18 -07:00
Adam Brown
92e7578aa7 Add Postgres schema-upgrade verification test suite
Verifies that databases upgraded via Schema.migrate end up structurally
identical to a fresh install, and that data written under the v1 schema
survives migration to the latest version. Restores the schema/data
guarantees the SQLite migration tests provided before the Postgres move.

The checked-in v1_baseline.sql is the immutable upgrade-from snapshot;
new schema versions are picked up automatically via Schema.version.
2026-06-16 02:23:23 -07:00
Adam Brown
90feb01a4d Improve monitoring dashboard styling for more stats 2026-06-16 01:57:55 -07:00
Adam Brown
27d33a6995 Improve monitoring dashboard styling for active users 2026-06-16 01:42:03 -07:00
Adam Brown
ed7ba52bf7
Update ROADMAP.md to reflect completed tasks
Removed completed tasks from the roadmap.
2026-06-16 01:22:00 -07:00
Adam Brown
9b3a2dc7d6
Update roadmap by removing future features
Removed 'Editor Requests' and 'Publish on Web' features from the roadmap.
2026-06-16 01:21:13 -07:00
Adam Brown
49ba08b55a Prepared for release: v3.3.1 2026-06-16 00:34:56 -07:00
Adam Brown
a6147ab72a Fix server tests 2026-06-16 00:33:25 -07:00
Adam Brown
a3cc091806 Not an error, this is an expected path, no need to log it 2026-06-15 23:59:22 -07:00
Adam Brown
c533916395 Change string 2026-06-15 23:58:47 -07:00
Adam Brown
091590943d Update text editor
Added shortcuts
2026-06-15 23:49:14 -07:00
Adam Brown
e535b68f46 Distinguish client-fault errors from server faults on the monitoring dashboard
The Recent Errors list rendered every entry as loud red, even though most
were scanner/bot probes hitting /api without a protocol header. Give those a
quieter treatment and surface the HTTP status each error resolved to.

- Record the resolved HTTP status on error_log (folded into the v4 migration;
status column added last to match ALTER's physical column order).
- Classify only deliberately-marked client/transport faults as 4xx via a new
HttpStatusException hierarchy; everything else stays 500 so genuine server
bugs aren't hidden as client errors. The protocol enforcer now throws
UnsupportedProtocolVersionException instead of a generic korlibs exception.
- Apply the classified status to /api responses only; web routes keep their
500 + servererror page behavior.
- Render 4xx as amber warnings and 5xx as red errors, with a status badge and
severity icon, on the errors page.
2026-06-15 21:06:56 -07:00
Adam Brown
5e963c48ec Don't count the story owner as a reader of their own story
The public story route now reads the logged-in viewer's id from the session
and skips the reader tally when it matches the story owner, so an author
browsing their own published / shared story doesn't inflate their count.
2026-06-15 17:44:00 -07:00
Adam Brown
501dec8100 Fix error-rate metric missing exception-driven errors
The Error Rate graph read from MetricsCollector error counts recorded on
RoutingCallFinished, which never fires when a handler throws. Exceptions
mapped to 500 by StatusPages were therefore invisible to the time series
even though they were logged to the recent-errors list.

Record metrics on ResponseSent instead, so the final (exception-mapped)
status is observed, and key on the route template stashed during routing.
2026-06-15 17:31:00 -07:00
Adam Brown
0cc3c754a4 Add per-story reader counter and unique active-users metrics
Two privacy-preserving usage metrics for the sync server, accumulated in
memory and flushed by the monitoring maintenance job:

- Unique active users (sync + web) over 24h/7d/30d on the admin dashboard.
- Best-effort unique readers of published / shared stories, identified by a
  cookieless daily-salted visitor hash: IP + user-agent are hashed in-request
  and never stored, and the salt rotates daily in memory, so reads can't be
  linked across days or back to an IP. Authors see an all-time total plus a
  30-day daily trend on their story page; admins see aggregate window counts.

Reader rows dedupe per (project, day) and are purged on the metrics retention
window, with each day's count rolled into a per-story lifetime total first so
the all-time number survives the purge.

Adds the user_activity and published_story_reader[_total] tables (schema v4),
consolidates UTC-day truncation into a shared helper, and formats the trend
labels in UTC to match the buckets.
2026-06-15 02:01:32 -07:00
Adam Brown
e43833b7a5 Codereview changes 2026-06-15 00:26:19 -07:00
Adam Brown
0f0f3b1cfe Add Active Users metric to server monitoring dashboard
Track unique logged-in users (real accounts, not anonymous visitors)
split by activity: syncing vs. authenticated web sessions.

- New user_activity table (dedup per user/type/hour) + migration 3.sqm,
  DB version 4. Distinct counts via COUNT(DISTINCT user_id); the additive
  histogram used for API metrics can't answer distinct-user counts.
- In-memory UserActivityCollector flushed by MonitoringMaintenanceJob,
  mirroring MetricsCollector. Gated end-to-end by the master monitoring
  switch (MonitoringConfig.enabled) and purged on metricsRetentionDays.
- Recorded at two hook points: sync (ProjectsRoutes.beginProjectsSync)
  and any authenticated web render (Frontend.withDefaults).
- Overview dashboard shows 24h/7d/30d rollups (DAU/WAU/MAU) plus a daily
  active-users trend line (Sync vs Web) over the last 30 days.
2026-06-15 00:06:42 -07:00
Adam Brown
74407030e1 Add tablet screen previews, Screen-prefix naming, and preview backgrounds
- Tablet variants: new TabletPreviewSurface provides a wide ScreenCharacteristics
  so screens render their expanded layout; @Preview(widthDp/heightDp) sizes the
  canvas to a landscape tablet. Added for ProjectStats, ProjectSettings,
  TimeLineOverview, BrowseEntries, BrowseNotes, and ViewEntry.
- Naming: prefix every top-level screen preview (phone and tablet) with "Screen"
  so they group together and are easy to pick out from component previews.
- Backgrounds: the shared Padded wrapper now applies the app theme + surface
  background; ProjectCard and SceneItem previews now render against the app
  background instead of the renderer's transparent default.
2026-06-14 17:36:48 -07:00
Adam Brown
ccc6c2245e Make all Compose previews renderable
The compose-preview renderer invokes @Preview functions reflectively and
cannot access private top-level functions, so most existing previews failed
to render with IllegalAccessException. Drop `private` from every @Preview
function.

Also fix the remaining render failures:
- Wrap encyclopedia/scene previews that touched Koin in KoinApplicationPreview.
- Register a minimal coil3 ImageLoader in the preview Koin graph so AsyncImage
  screens (ViewEntry, CreateEntry) resolve it.

Result: 88/88 previews render, 0 errors (was 19/88).
2026-06-14 17:10:47 -07:00
Adam Brown
42a1f48089 Provide fake SpellCheckRepository to preview Koin graph
MarkdownEditField injects SpellCheckRepository, so every editor-based
preview (CreateNoteUi, CreateTimeLineEventUi, ...) crashed at render with
a missing Koin definition. Register a real SpellCheckRepository backed by
in-memory settings datasources in KoinApplicationPreview's base module.
Lazy single, so non-editor previews are unaffected.
2026-06-14 16:34:02 -07:00
Adam Brown
7b83e8edf2 Wire up compose reports 2026-06-14 16:28:36 -07:00
Adam Brown
8ae81a631e Add Compose previews for router destination screens
Cover the destination screens behind the project-home, notes, timeline,
and story-editor routers with @Preview fixtures:
- ProjectStatsUi, ProjectSettingsUi (project home)
- CreateNoteUi (notes)
- TimeLineOverviewUi, CreateTimeLineEventUi, ViewTimeLineEventUi (timeline)
- OutlineOverviewUi (story editor dialog)

Each preview hand-rolls a fake component implementing the Decompose
interface so the screen renders without runtime dependencies.
2026-06-14 12:01:04 -07:00
Adam Brown
c85c324909 Cover EncyclopediaRepository image operations
Add tests over a real datasource/fake filesystem for setEntryImage
(store and null-clears), loadEntryImage/getEntryImagePath round-trip,
calculateEntryImageHash, removeEntryImage (success and failure branches),
and ensureEntriesLoaded caching.
2026-06-14 11:46:51 -07:00
Adam Brown
fbf04940db Cover TimeLineRepository sync-path methods
Add tests for reIdEvent, updateEventForSync (replace and append),
storeTimeline flushing in-memory edits, getTimelineEvent, and the
server-synced markForSynchronization branch.
2026-06-14 11:40:46 -07:00
Adam Brown
f21f6ad0d3 Cover SpellCheckRepository locale loading
Drive the repository over a real GlobalSettingsStore (mocked datasources)
and a mocked platform spell-check factory: loads a checker for the
configured locale, skips unsupported locales, reloads on a locale change,
ignores unrelated settings changes, plus the toSpLocale mapping.
2026-06-14 11:37:41 -07:00
Adam Brown
36686e261c Cover ProjectBackupRepository backup lifecycle
Add tests for getBackupsForProject, deleteBackup (success, missing,
failure), cullBackups (over and under budget), and the real
createBackup/restoreBackup zip round-trip over a fake filesystem.
2026-06-14 11:25:37 -07:00
Adam Brown
8cdb193d5b
Kover hygiene + server domain coverage (#611)
* Exclude generated code from kover coverage reports

Generated SQLDelight query/DB code and Compose Multiplatform resource
accessors were counted against coverage, masking the real figure for
hand-written code (merged line coverage read ~47% with them, ~61%
without). Filter them out so the metric reflects code we actually own.

* Test PasswordResetRepository

Cover the password-reset flow end-to-end: enumeration-safe request
handling (unknown account, rate limiting, email-send failure), token
validation states (invalid/used/expired/valid), and password reset
(invalid token, weak password, and the success path that updates the
hash, logs out all devices, and consumes the token).

* Test the review invite and submitted mailers

Cover email composition for both review mailers: the rendered HTML and
text bodies carry the project, author, note, and review link; the
note-present/absent and expiry/no-expiry branches; the singular vs
plural suggestion tally; and that the email service's result (including
failures) is passed through to the caller.

* Test AccountsRepository token refresh

Cover refreshToken: a matching install token mints a fresh valid token,
while a missing token or a mismatched refresh hash fails.
2026-06-14 11:13:54 -07:00
Adam Brown
ee8bf2c65b Add tier 3 coverage: project data sync + synchronizer dispatch
Cover ProjectDataRepository end-to-end over a fake filesystem (load
caching, user edits invalidating the project hash, sync-only updates),
fill out every branch of ProjectDataSyncOperation (no-op, fast-forward,
upload, and conflict resolve/abort paths) by driving real repository and
broker collaborators, and exercise the EntitySynchronizers facade's
type dispatch (get, findEntityType, reIdEntry phantom skip, conflict
routing) for all entity types.
2026-06-14 01:38:08 -07:00
renovate[bot]
d9d4eb70b3
Update codecov/codecov-action action to v7 (#567)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-14 01:34:22 -07:00
renovate[bot]
0341c7e087
Update actions/cache action to v5 (#555)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-14 01:34:03 -07:00
renovate[bot]
fa5c8d9e1e
Update plugin ee.schimke.composeai.preview to v0.15.2 (#540)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-14 01:33:33 -07:00
renovate[bot]
c7e81d3ae9
Update coil to v3.5.0 (#602)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-14 01:33:19 -07:00
Adam Brown
410d043d77
New Crowdin updates (#603)
* 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]
2026-06-14 01:32:37 -07:00
Adam Brown
4e1850f9c5
Expand sync + tree test coverage (#604)
* Expand test coverage for sync synchronizers and ImmutableTree

Add a Classical-style suite for ImmutableTree covering the previously
untested query API (indexOf, findBy, isAncestorOf, getBranch, coordinate
round-trips, iterator exhaustion, equals/hashCode).

Rewrite ClientEncyclopediaSynchronizer tests to drive real repository,
service, and datasource collaborators over a fake filesystem instead of
mocks, asserting observable on-disk state, image base64 round-trips, and
create-vs-update behavior.

Extend ClientSceneSynchronizer tests with createEntityForId,
deleteEntityLocal, archived-scene-from-server, store-content failure,
and reIdEntity branches.

Extend EntityTransferOperation tests with the download/heal branches:
not-modified, not-found remote delete, stale-hash forced-upload heal,
failed-store logging, and the onlyNew upload path.

* Fail entity transfer when a downloaded entity cannot be stored

downloadEntry returned CResult.success() even when storeEntity failed,
so a failed download was logged but never marked the transfer as
unsuccessful, masking sync failures from the operation's allSuccess
tracking. Return a failure result in that case so the transfer reflects
it.

* Add tier 2 sync coverage: uploadEntity, transfer + scene branches

Cover the EntitySynchronizer.uploadEntity base method end-to-end through
the encyclopedia synchronizer's classical harness (success, force, plain
failure, conflict resolution, and failed resolution), the remaining
EntityTransferOperation download/upload failure branches, and the scene
group move-parent and unarchive-on-active paths.
2026-06-14 01:26:05 -07:00
Adam Brown
ca692964ef Fix server dashboard buckets roll over 2026-06-14 01:22:35 -07:00
Adam Brown
1ba29ee393 Bring more javascript under test 2026-06-14 00:13:41 -07:00
Adam Brown
5272c48daa Fix editorial review character escaping 2026-06-13 23:27:04 -07:00
renovate[bot]
b31e7d1da8
Update filekit to v0.14.2 (#599)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-13 21:15:22 -07:00
Adam Brown
7982cf10f6
Fix mobile nav drawer rendering beneath page content (#600)
The .site-header had backdrop-filter applied directly, which made it the
containing block and stacking context for the fixed mobile nav drawer
nested inside it. This trapped the drawer in the header's compositing
layer, so it rendered beneath page content like the home page hero image
regardless of its z-index.

Move the frosted-glass background to a ::before pseudo-element so the
header no longer establishes a backdrop-filter context around the drawer.
The visual appearance is unchanged, but the drawer now positions against
the viewport and stacks correctly above page content.
2026-06-13 21:14:43 -07:00
Adam Brown
df2468747e
Add keyboard shortcuts for bold, italic and strikethrough (#601)
The markdown editor only exposed inline styles through format-bar buttons;
the underlying composetexteditor library handles editing/navigation shortcuts
(Ctrl+C/V/X/Z/Y, arrows, etc.) but has no bindings for bold/italic, so those
keys were simply dropped.
2026-06-13 20:54:24 -07:00
Adam Brown
18bcc4652f Prepared for release: v3.3.0 2026-06-13 12:44:51 -07:00
Adam Brown
af08da3163 Harden database tests against port collisions 2026-06-13 12:44:06 -07:00