Skip to content

feat: the conversation file says when its backup was made, and the later backup decides a message's mark and text - #1966

Merged
mbeisser1 merged 27 commits into
mainfrom
feat/1924-backup-taken-at
Oct 6, 2026
Merged

mbeisser1 merged 27 commits into
mainfrom
feat/1924-backup-taken-at

Conversation

@mbeisser1

Copy link
Copy Markdown
Member

Base note: this branch is built on feat/1906-export-crate-name (#1961, the message-crate-pull → message-crate-export rename), which had not merged into main when this was opened. Until #1961 merges, this diff includes the rename's commits; after it merges, only the #1924 commits remain. The rename's latest head is merged in (a7ea0650b).

Feature Description

Every conversation file now says when the backup it was read from was made, and the import uses that date to decide between two copies of one message from one source. The copy from the later backup gives the message its deletion mark (mark or no mark), its text and its earlier versions, in one import in either file order and across imports in either order. An older backup appended after a newer one changes nothing. A file that does not say keeps today's rules: marks add, edits compare their version times. Import details under Settings → Storage show the backup a run read and when it was made.

This is the decision of 2026-10-05 on #1741 (option 3) and #1804 (option 1).

User Story

As a person importing more than one backup of the same phone, I want the newer backup to decide a message that changed between them (recovered after deletion, unsent, edited) so that what Message Crate shows matches my phone now, whichever backup I import first.

Implementation Details

Conversation file (schema 10 → 11). ExportMeta gains backup_taken_at_unix_ms: Option<i64>. SCHEMA_VERSION is 11, and check_schema_version refuses a version-10 file by name, with a test (refuses_a_version_10_file_by_name). Nothing is upgraded.

Where each exporter reads the date.

Source Date
Apple Messages, iPhone backup Manifest.plist Date (new ios_backup::ios_backup_date_unix_ms; the plist is readable in an encrypted backup, so the Apple Messages Reader protocol is unchanged)
Apple Messages, Mac chat.db the file's modification time
WhatsApp, iPhone backup Manifest.plist Date
WhatsApp, Android modification time of msgstore.db.crypt15 / msgstore.db (the form's file wins)
WhatsApp, ready-made result.json the JSON's modification time
SMS Backup & Restore root backup_date attribute, else the file's modification time; a conversation read from two files takes the newer
iMazing newest modification time of the CSVs (the export date)
OpenExtract, GO SMS Pro, SMS Backup+ newest modification time of the files read
Export Run (message-crate-export) newest backup_taken_at of the conversation's messages
demo seed the settings' reference time

Formats. JSON/JSONL through serde; CSV gains a backup_taken_at_unix_ms column; EML and MBOX gain an X-ME-Backup-Taken-At-Unix-Ms header (the formats' existing X-ME-* prefix). A value that is not a whole number is refused.

Import. Staging stores the date on each staged row (staging_messages.backup_taken_at), and messages.backup_taken_at keeps the date of the backup that decided the stored copy. One SQL rule, later_backup_sql in db/staging.rs, says whether a staged copy is from a later backup (NULL when either side has no date):

  • promote_deletion_marks: a later-backup row sets its mark or clears the held one; an earlier/same-backup row changes nothing; undated falls back to "a mark adds".
  • write_edit_map: a later-backup row goes into the edit map when its text or version list differs (compared whole with json_group_array(... ORDER BY id)), whatever the version times say; undated falls back to later_edit_sql.
  • promote_backup_dates (new phase after the edits) moves the stored date forward.
  • In one import, add_staged_copy applies the same rule to the second copy of a staged message (take_staged_copy_from_later_backup); undated keeps take_later_staged_copy and now also adds the copy's mark rather than dropping it to ON CONFLICT DO NOTHING.

The duplicate flag follows the text, because the dedupe compares the text; a test covers it in one import with dedupe on. #1805 (an append with dedupe off leaves a changed message's flag) is not part of this change.

The Import Run records the newest date its files name (db::imports::note_backup_taken_at, per staged conversation).

Key Files Changed

  • crates/libs/ir/src/lib.rs, schema_version.rs - the field, version 11, the refusal
  • crates/libs/ir-format/src/{write,read_csv,read_mail}.rs, crates/libs/mail/src/* - CSV column and mail header
  • crates/libs/ios-backup/src/backup.rs - ios_backup_date_unix_ms
  • crates/core/message-crate-core/src/pipeline.rs - export_meta takes the date; file_modified_unix_ms, newest_file_modified_unix_ms
  • crates/exporters/* and crates/libs/sbr/src/read.rs - each exporter fills the date
  • crates/libs/export/src/{project,run}.rs - an Export Run writes the stored date back
  • crates/server/server/src/db/staging.rs, imports_api/{staging,promote}.rs - the later-backup rule
  • crates/server/server/src/imports_api/tests/backup_dates.rs - promotion and API tests
  • web/src/screens/settings/storage/ImportDetailPanel.tsx, storageUtils.ts - the Backup line
  • docs/architecture/contacts-identities-and-messages.md - the rule and its reason

HTTP API Changes

  • Message.backup_taken_at (string, RFC 3339 UTC, nullable) on every route that answers a message (GET /v1/messages, conversation messages, and the export read path): when the backup that decided the message's mark and text was made.
  • ImportRunSummary.backup_taken_at / ImportRun.backup_taken_at (string, nullable) on GET /v1/imports, GET /v1/imports/{id}, GET /v1/accounts/{id}/imports[/{id}] and the run answers of PATCH/complete: the newest backup date the run's files name. Not on OwnerImportRun: the owner's view of another account's run carries nothing of its backup.
  • AccountImportRun's two variants are boxed in Rust (Clippy large_enum_variant); the JSON is unchanged.
  • OpenAPI regenerated (docs/src/assets/openapi.json) and web types regenerated (web/src/lib/serverApi.types.ts).

Database Changes

  • No schema changes
  • schema/sql/*.sql changed (every database rebuilds empty and re-imports; the fingerprint is computed, nothing to bump)

staging_messages.backup_taken_at, messages.backup_taken_at, imports.backup_taken_at (all TEXT, the form timestamp holds). The conversation file format changes too: schema version 11, and version 10 is refused.

How to Test

  1. cargo test -p message-crate-server --lib backup_dates — the two-backup cases: the deletion mark and the text (the An append keeps the older text when a later backup only unsent a part #1804 scenario) in one import in both file orders and across two imports in both orders, the dates swapped, an append of the older backup changing nothing, the duplicate flag, the no-date fallback, the Import Run's and a message's backup_taken_at.
  2. cargo test -p message-ir-format backup — every format (JSON, JSONL, CSV, EML, MBOX) keeps the date, and CSV refuses a non-number; cargo test -p message-ir schema_version — version 10 refused.
  3. One test per exporter that the date is read from its source: ios-backup (the_backup_date_is_the_manifest_s_date, fixture tests/fixtures/dated-backup/Manifest.plist), imessage-ir-exporter (an_iphone_backup_is_dated_by_its_manifest, a_mac_chat_db_is_dated_by_its_modification_time, and through the real reader every_conversation_file_says_when_the_chat_db_was_last_written), whatsapp-exporter (an_iphone_backup_is_dated_by_its_manifest, an_android_backup_is_dated_by_its_database_file), sms-backup-restore-exporter (the_backup_date_is_the_files_backup_date_attribute with fixture tests/fixtures/dated_backup.xml, the modification-time fallback, two backups in one directory), imazing-exporter, openextract-exporter, go-sms-pro-exporter, sms-backup-plus-exporter (modification time), message-crate-export (a_document_says_the_newest_backup_its_messages_came_from).
  4. cd web && npx vitest run src/screens/settings/storage — the Backup line in Import details and importBackup.
  5. In the app: import two backups of one phone, then open Settings → Storage → Import history → a run; the Backup line names the backup and when it was made.

Each new test fails without the fix, checked by hand: with later_backup_sql forced to NULL and the in-import copy rule and additive copy mark switched off, 5 of the 7 promotion tests fail (the other two cover the new API fields, which do not exist without the change); with the date reads in file_modified_unix_ms, sbr::read::backup_date and ios_backup_date_unix_ms switched off, every exporter test above fails; with the Backup line removed from ImportDetailPanel, both new Vitest cases fail.

Checklist

  • Code follows the project's style guidelines
  • Self-reviewed the code
  • Added unit tests for new functionality
  • Added integration tests where applicable
  • Existing tests pass locally
  • Updated documentation
  • ./scripts/check-pr.sh passes and formatter rewrites are committed

Run locally after merging the rename's head: ./scripts/check-pr.sh, cargo test --workspace --no-fail-fast, cargo test --manifest-path src-tauri/Cargo.toml, cd web && npm test && npm run build, ./scripts/check-generated-api-types.sh, cd docs && npm run check && npm run build.

Related Issues

Closes #1924

Unblocks #1741 and #1804, whose decisions this implements; their own acceptance tests and closing are left to them. Related: #1805 (an append with dedupe off still leaves a changed message's duplicate flag), #1923 (the next schema bump), #1906 / #1961 (the base).

Screenshots / Demo

Not captured. Import details gains one line under the figures: Backup, the path the desktop app recorded, and "Made " or "The backup does not say when it was made".

Deployment Notes

The database rebuilds empty on first start (schema change). Conversation files at schema version 10 are refused; export the backup again with this build. CHANGELOG entry under 0.11.0 (Features and Upgrading).

The SMS Backup & Restore XML writer does not write backup_date back yet, so an Export to XML read back in is dated by the file's modification time.

🤖 Generated with Claude Code

mbeisser1 and others added 18 commits October 5, 2026 23:15
CONTEXT.md names the operation Export and lists Pull under its Avoid, but
the crate that runs an Export from a server, its types, the desktop
command, the state file and the recorded tool name all said Pull.

- The crate is message-crate-export at crates/libs/export/ (library
  message_crate_export), with ExportConfig, ExportReport, the private
  Export type, ExportJournalEvent, ExportJournalState and
  EXPORT_JOURNAL_NAME.
- The state file is .message-crate-export-state.jsonl. The old file is
  not read.
- The server records the tool as message-crate-export. The old name is
  not mapped.
- The desktop command is export (src-tauri/src/commands/export.rs,
  ExportArgs), and the web app calls it as invokeExport.
- CLAUDE.md, AGENTS.md, the developer pages, the Export guide, the
  OpenAPI document and the crate READMEs name the new crate. ADR 0001 and
  ADR 0012 keep their text and gain a dated note.

Closes #1906

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AGENTS.md keeps file paths, tool names and crate names out of CHANGELOG
entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Export's own working directory and the step that fills it said pull,
which CONTEXT.md avoids for Export. The directory is .exported (EXPORTED,
the exported field on ExportDir), and the step is the fetch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fetch already names fetching an Asset, so the step before the format step
is the export step, after the invokeExport call it makes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ExportMeta gains backup_taken_at_unix_ms, and schema_version goes from 10
to 11; a version-10 file is refused by its version. Each exporter fills
the date from its source: an iPhone backup's Manifest.plist date for
Apple Messages and WhatsApp, a Mac chat.db's modification time, the
backup_date attribute of an SMS Backup & Restore file, the newest
modification time of the files read for iMazing, OpenExtract, GO SMS
Pro, SMS Backup+ and an Android WhatsApp database. The demo seed dates
its backups at its reference time.

ir-format carries the date in every format it writes and reads: JSON
and JSON Lines through serde, CSV in a backup_taken_at_unix_ms column,
EML and MBOX in an X-ME-Backup-Taken-At-Unix-Ms header. A value that is
not a whole number is refused rather than read as no date.

Part of #1924.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Staging keeps each staged row's backup date (staging_messages
.backup_taken_at), and messages keeps the date of the backup that
decided the stored copy. Between two copies of one message from one
source, when both backups have a date, the copy from the later backup
gives the message its deletion mark, mark or no mark, and its text and
earlier versions, whatever the versions' times say; a copy from an
earlier or the same backup changes neither. This holds in one import in
either file order and across imports in either order, so the duplicate
flag, which follows the text, does too. Where either backup has no date,
the rules for files without one hold: a mark adds, now also from the
second file of one import, and an edit is taken when its newest earlier
version is newer.

The Import Run records the newest backup date its files name, and the
HTTP API answers it as backup_taken_at on ImportRun and
ImportRunSummary. A message answers its own backup_taken_at, which an
Export Run writes back into the conversation file as the newest date of
the conversation's messages.

The schema change rebuilds the database; nothing migrates.

Part of #1924, #1741, #1804.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…made

Settings → Storage → Import history opens a run's details with a Backup
line: the backup the desktop app recorded for the run, and when that
backup was made, from the run's new backup_taken_at. A backup that
records no date says so. The owner's view of another account's run
carries neither and shows nothing.

Part of #1924.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The common message page documents export.backup_taken_at_unix_ms,
where each exporter reads it from, and the rule the import applies; the
CSV and mail references list the new column and header; the import and
storage pages say which backup wins and where the date shows. The
CHANGELOG entry for 0.11.0 says files at schema version 10 are refused.

Closes #1924.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…x row back

#1957 reindexes a stored message only when a staged row carries a mark,
because a promotion never cleared one. With #1924 a later backup can clear
an Unsent mark, and that message stayed out of the search index. The
promotion now names every message whose mark it changes, set or cleared,
in _promote_mark_map, and the reindex set reads that table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@mbeisser1 mbeisser1 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of 10a0946 (origin/main merged in, so the diff against main holds only the #1924 work). Findings by axis: Merge review (the merge of main, with #1957's search index change) 2, Spec 3, Standards 9, Correctness 3. Three findings have no line in the diff and follow in a top-level comment.

Comment thread crates/server/server/src/db/schema.rs
Comment thread crates/server/server/src/db/staging.rs
Comment thread crates/server/server/src/imports_api/staging.rs
Comment thread crates/server/server/src/models.rs Outdated
Comment thread crates/exporters/sms-backup-restore-exporter/src/read.rs
Comment thread crates/core/message-crate-core/src/pipeline.rs
Comment thread web/src/screens/settings/storage/storageUtils.ts
Comment thread crates/exporters/imessage-ir-exporter/src/run.rs Outdated
Comment thread crates/libs/export/src/run.rs Outdated
Comment thread crates/core/message-crate-core/src/pipeline.rs
@mbeisser1

Copy link
Copy Markdown
Member Author

Findings with no line in the diff (review of 10a0946).

  1. Spec · spec — "message-crate-pull writes the stored value back out." The export writes the date into every ir-format format, but the SMS Backup & Restore XML writer does not write backup_date. An Export to XML, read back in, is dated by the file's modification time instead of the stored date, which can reverse which backup counts as later. Fix: write the stored date as the root backup_date attribute, with a round-trip test, or file an issue.

  2. Standards · bug — crates/libs/sbr/src/lib.rs, line 157: the writer emits <smses count="N"> with no backup_date. The reader then falls back to the modification time, which is when Message Crate wrote the file, so an old backup converted or exported to XML and imported later reads as the newest backup, and its marks and text overwrite those of a backup that is really later. Fix: write backup_date from export.backup_taken_at_unix_ms.

  3. Standards · judgement — docs/architecture/contacts-identities-and-messages.md, lines 408–410: the title rule's reason still says "The time a backup was made is not recorded by every source". Every file now carries a date field, so the reason reads stale. Fix: rewrite the reason, or file an issue to move titles onto the backup date.

mbeisser1 and others added 6 commits October 6, 2026 00:22
…out one

Two reads of one Mac's chat.db within one second carry the same date,
and the later-backup rule read them as the same backup, so an unsend or
an edit between them was lost. Equal dates now cannot decide, in SQL
(later_backup_sql) and in Rust (the new later_backup helper, which the
in-import copy rule uses instead of restating the comparison), and the
undated rules hold: they change nothing for the same backup read again.
add_staged_copy_mark no longer returns a flag its caller drops. The
architecture doc says the date is kept to the second.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Messages keeps chat.db in write-ahead mode, so a row written since the
last checkpoint moves only chat.db-wal's time, and two reads of one Mac
could carry the same date or an iPhone backup could read as later than
a deletion the Mac already held. The date is now the newest
modification time of chat.db, chat.db-wal and chat.db-shm. The format
doc also says equal dates fall back to the rules for files without one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sages share it

An Export Run stamped every message of a conversation file with the
newest backup date of any of them. Imported elsewhere, a message decided
by an older backup then claimed the newest date and ignored a backup made
between the two, and a message with no date gained one. The file carries
one date for all its messages, so it now names one only when every
message has the same, and none otherwise, which keeps the rules for
files without a date.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The SMS Backup & Restore writer left backup_date off the root element,
so an Export or conversion to XML read back in was dated by when Message
Crate wrote the file and read as the newest backup. The writer now
writes the newest backup date of the conversations it holds, the rule
the reader follows for a conversation read from two files, and leaves
the attribute out when none has one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 0.11.0 entries said "schema version 10", which the changelog rule
leaves out; they now say which files are refused in the reader's words.
The group title rule's reason no longer says the backup's date is not
recorded: it says why the date the file now carries still does not
decide a title. http-api.md says the owner's view of an Import Run
carries the run's own times and nothing of the backup it read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…5-tuple

Adding the backup date made the result of reading the source a 5-tuple
and reindented the whole branch. ReadJson names each value, and the
branch is back at its old indentation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mbeisser1 and others added 3 commits October 6, 2026 00:28
later_backup returns a BackupOrder (Later with the date, Earlier,
Undecided) rather than an Option<bool> its caller had to unpack again.
The SBR writer writes its root element once, and notes a conversation's
backup date only when the conversation writes an SMS or MMS, so a
WhatsApp or iMessage conversation left out does not move the file's
date. The WhatsApp run's ConversionInput is destructured where it is
built. Two doc comments are rewrapped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n-at

#1963 made every stored time three-digit milliseconds and changed
format_utc_timestamp to take milliseconds. The backup date now goes to
it whole, so it is stored to the millisecond like every other time, and
the docs that said "to the second" say so. CHANGELOG keeps both sides'
Upgrading entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two paragraphs the merge of #1963 edited ran past the wrap width. Test
fixtures that stand for a stored backup date now use the three-digit
millisecond form the server writes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mbeisser1
mbeisser1 marked this pull request as ready for review October 6, 2026 04:41

@mbeisser1 mbeisser1 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of the fix commits (47aff7c, 5de3d2e, 9cc6ce1, 973edb5, edeada4, c431833), and the merge review of the second merge of main (f4f5a07, which brought #1963's millisecond times and #1964). Standards 4, Correctness 1, Merge review 1. Each is answered in its thread.

Comment thread crates/server/server/src/imports_api/staging.rs
Comment thread crates/server/server/src/db/staging.rs
Comment thread crates/libs/sbr/src/lib.rs
Comment thread crates/exporters/whatsapp-exporter/src/run.rs
Comment thread crates/exporters/sms-backup-restore-exporter/src/write.rs
Comment thread crates/server/server/src/db/staging.rs
@mbeisser1

Copy link
Copy Markdown
Member Author

Answers to the top-level findings:

  1. Spec · spec — "message-crate-pull writes the stored value back out." … the SMS Backup & Restore XML writer does not write backup_date.
  1. Standards · bug — crates/libs/sbr/src/lib.rs, line 157: the writer emits <smses count="N"> with no backup_date.

Fixed in 973edb5 and f9bac68: SbrBackupWriter::note_backup_date collects the dates and finish writes the newest as the root backup_date, the rule the reader uses for a conversation read from two files; a conversation is noted only when it writes an SMS or MMS. Tests the_backup_date_noted_survives_a_round_trip (sbr) and the_backup_date_comes_from_the_conversations_written (exporter). The one-date-per-file limit is deferred to #1967.

  1. Standards · judgement — docs/architecture/contacts-identities-and-messages.md, lines 408–410: the title rule's reason still says "The time a backup was made is not recorded by every source".

Fixed in edeada4: the reason now says the file's backup date is missing where a source records none and for several sources is a modification time that copying can change, so it does not decide a title.

@mbeisser1

mbeisser1 commented Oct 6, 2026 •

Copy link
Copy Markdown
Member Author

Review summary

Reviewed against the spec (#1924, with the #1741 and #1804 decisions it implements) and the standards (CLAUDE.md, AGENTS.md, writing style, CONTEXT.md, contacts-identities-and-messages.md, http-api.md, ADR 0012, ADR 0002). CI run 37414909306 on fea14d2: success.

Axis Findings Fixed Declined Deferred
Merge review (main with #1957, then with #1963/#1964) 3 3 0 0
Spec 3 2 1 0
Standards 9 6 (two of them in part) 2 in full, 3 in part 1
Correctness 3 2 1 0
Standards re-review 4 4 0 0
Correctness re-review 1 1 case 1 case 0

Merges of the base:

No commits for CI failures. No Spec skip. No user threads.

@mbeisser1
mbeisser1 merged commit 7a8435e into main Oct 6, 2026
30 checks passed
@mbeisser1
mbeisser1 deleted the feat/1924-backup-taken-at branch October 6, 2026 04:56
mbeisser1 added a commit that referenced this pull request Oct 8, 2026
The fix landed with #1966, whose Features entry already describes it.
Drop the two Fixes bullets that repeated it under a later date.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
mbeisser1 added a commit that referenced this pull request Oct 8, 2026
…nt part included (#1741, #1804) (#1983)

* test(import): the later backup decides a message whose part was unsent

The #1924 tests ran the #1804 scenario without its unsent part: backup B
carried no mark. The new test gives B the Unsent mark its unsent part
brings, and checks every order, one import of both files in either order
included: with B the later backup the message holds B's text, versions and
mark and search finds neither copy's text; with the dates swapped it holds
A's with no mark and search finds A's text; with no dates the version
times pick A's text and B's mark adds, as #1801 left them.

Part of #1741, #1804.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(changelog): the later backup decides a message's mark and text

Two Fixes entries for the 0.11.0 Importing list: a mark is cleared by a
newer backup that no longer has it, and the second file of one import no
longer loses its mark (#1741); a newer backup that unsent a part gives the
message its text whatever the edit times say (#1804).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(changelog): cite #1741 and #1804 on the entry that fixed them

The fix landed with #1966, whose Features entry already describes it.
Drop the two Fixes bullets that repeated it under a later date.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(import): one #1804 scenario, checked in both search indexes

Fold the unsent-part test into the_later_backup_decides_the_text_whatever_the_version_times_say:
backup B now carries the Unsent mark, and the test checks search and the
undated files there. It also counts earlier versions in
message_versions_fts, which keeps them for an Unsent message, so a stale
version of backup A or a missing one of backup B fails the test.

every_order returns each order's name and database with what it holds,
so no test copies its order names or database path. text_hits sits with
the other helpers and replaces the inline count in the searchable test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(import): a mixed import keeps the dated file's backup date in either order

When one import held a dated file and an undated one, the staged message
kept the date only when the dated file was read first. Read second, the
dates could not decide, nothing set the date, and a later import fell
back to adding marks: a backup made later without the mark left the
message Unsent. The staged message now takes the dated copy's date when
it has none.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(import): later_edit_sql is the fallback for undated copies

Its doc comment still called it the one rule for which copy is the later
backup, which later_backup_sql now is. Also part later_backup_sql's two
paragraphs, which ran together.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Revert "fix(import): a mixed import keeps the dated file's backup date in either order"

This reverts commit 6c96678. Giving the staged row the dated copy's
date whenever an undated copy also contributed makes the dated copy's
date decide for the undated copy too at promotion, which drops a mark
or an edit the undated copy brought when the stored message came from a
later backup. Neither rule stores what two separate imports store in
every order, so the order-dependence of a mixed import is filed as its
own issue rather than fixed here.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The conversation file says when the backup was made, so an append knows which backup is newer

1 participant