Skip to content

Commit 7e5d24a

Browse files
committed
chore: CHANGELOG and plan update for M3 completion
1 parent bae40d3 commit 7e5d24a

3 files changed

Lines changed: 57 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ All notable changes to Submersion are documented in this file.
1919
internal units at the reader boundary.
2020
- **MacDive (XML) source override** in the import wizard's detected-source
2121
dropdown, alongside the existing MacDive (CSV) option.
22+
- **MacDive SQLite import.** Direct import from MacDive's Core Data
23+
SQLite database — the most complete metadata path. Captures dives,
24+
sites, buddies, tags (as with XML), gear inventory, critters/marine
25+
life sightings, dive events, service records, and certifications.
26+
Cross-format deduplication via source UUIDs: if you've already
27+
imported the same dives via MacDive UDDF or XML, re-importing from
28+
SQLite won't create duplicates.
29+
- **MacDive (SQLite) source override** in the import wizard's
30+
detected-source dropdown, alongside MacDive (CSV) and MacDive (XML).
2231

2332
### Fixed
2433

@@ -42,6 +51,15 @@ All notable changes to Submersion are documented in this file.
4251
parsed but not yet persisted to the profile samples table. A future
4352
milestone will wire them through, likely via the dive-events table.
4453

54+
### Known limitations
55+
56+
- Profile samples (depth/time-series data) are NOT imported from
57+
MacDive SQLite. MacDive stores sample data in `ZDIVE.ZSAMPLES`
58+
using a proprietary binary format that isn't publicly documented
59+
and isn't standard bplist or any common compression. Users who
60+
need time-series profile data should use the MacDive UDDF import
61+
path instead, which decodes MacDive's UDDF profile correctly.
62+
4563

4664
## 1.4.5 (2026-04-21)
4765

docs/superpowers/plans/2026-04-21-macdive-sqlite-import.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,41 @@
2020

2121
---
2222

23+
## Milestone 3 Status — COMPLETE
24+
25+
- All 14 tasks landed; bplist decoder (Tasks 1-4) shipped with UID
26+
support after real-sample probing revealed NSKeyedArchiver format
27+
in ZTIMEZONE.
28+
- ZSAMPLES profile-sample decoding **descoped** — MacDive uses a
29+
proprietary binary format (entropy 7.85 bits/byte; not bplist; not
30+
zlib/gzip/lzma at any reasonable offset). Users wanting profile
31+
samples should use M1's UDDF import. M3 is the "rich metadata"
32+
path; UDDF remains the "sample data" path.
33+
- New `ImportFormat.macdiveSqlite` + source override. Detector
34+
chain: SQLite magic → Shearwater check → MacDive check → generic.
35+
- `MacDiveDbReader` validates schema (ZDIVE + ZDIVESITE + ZGAS +
36+
ZTANKANDGAS) and returns typed row graph keyed by PK; junctions
37+
as dive_pk → related_pks maps. Filters null-FK tombstones in
38+
ZTANKANDGAS discovered on real data.
39+
- `MacDiveDiveMapper` reuses M2's `MacDiveUnitConverter` and
40+
`MacDiveValueMapper`. Dive map keys match M2's `MacDiveXmlParser`
41+
exactly so the same `UddfEntityImporter` downstream consumes
42+
both sources uniformly.
43+
- `ImportDuplicateChecker` now short-circuits on `source_uuid`
44+
when incoming dives match existing `dive_data_sources.source_uuid`.
45+
Separate parameter map keeps the `Dive` entity unchanged
46+
(multi-source-per-dive semantics preserved).
47+
- Gated `@Tags(['real-data'])` test asserts against user's real
48+
6.7MB DB: 540 dives, 373 sites, 33 buddies, 39 tags, 32 gear —
49+
all with sourceUuid populated. Profile always empty per descope.
50+
- Full test suite passes.
51+
52+
Next: M4 (Photos) extends M2's XML parser and M3's SQLite reader
53+
to emit `imageRefs` on payloads and adds a photo-linking wizard
54+
step.
55+
56+
---
57+
2358
## File Structure
2459

2560
| File | Role | New / Modified |

test/features/universal_import/data/parsers/macdive_sqlite_parser_test.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ void main() {
2525
});
2626

2727
test('supportedFormats is macdiveSqlite', () {
28-
expect(MacDiveSqliteParser().supportedFormats, [
28+
expect(const MacDiveSqliteParser().supportedFormats, [
2929
ImportFormat.macdiveSqlite,
3030
]);
3131
});
3232

3333
test('parses synthetic MacDive SQLite end-to-end', () async {
34-
final payload = await MacDiveSqliteParser().parse(validBytes);
34+
final payload = await const MacDiveSqliteParser().parse(validBytes);
3535
expect(payload.entitiesOf(ImportEntityType.dives).length, 3);
3636
expect(payload.entitiesOf(ImportEntityType.sites).length, 2);
3737
expect(
@@ -56,15 +56,15 @@ void main() {
5656
db.dispose();
5757

5858
final otherBytes = Uint8List.fromList(await tmp.readAsBytes());
59-
final payload = await MacDiveSqliteParser().parse(otherBytes);
59+
final payload = await const MacDiveSqliteParser().parse(otherBytes);
6060
expect(payload.isEmpty, isTrue);
6161
expect(payload.warnings, isNotEmpty);
6262
expect(payload.warnings.first.severity, ImportWarningSeverity.error);
6363
});
6464

6565
test('returns error payload on totally invalid bytes', () async {
6666
final garbage = Uint8List.fromList(const [0, 1, 2, 3, 4]);
67-
final payload = await MacDiveSqliteParser().parse(garbage);
67+
final payload = await const MacDiveSqliteParser().parse(garbage);
6868
expect(payload.isEmpty, isTrue);
6969
expect(payload.warnings.first.severity, ImportWarningSeverity.error);
7070
});

0 commit comments

Comments
 (0)