sync + coverage: measure every suite, and open the app at desktop size - #76
Open
alichherawalla wants to merge 136 commits into
Open
sync + coverage: measure every suite, and open the app at desktop size#76alichherawalla wants to merge 136 commits into
alichherawalla wants to merge 136 commits into
Conversation
M0 of docs/SYNC_INTEGRATION_PLAN.md. Brings the public sync engine in so the pro integration can consume it; no behaviour wired yet. - Vendors shared/packages/sync -> desktop/packages/sync following the existing convention (@offgrid/clipboard|design|models|rag are git-tracked copies consumed via file: deps). Records provenance (offgridVendoredFrom: commit 9b671b5) in the vendored package.json, because the existing copies have silently DRIFTED from shared/ and that should be visible. - Adds runtime deps the engine needs: bonjour-service (pure-JS mDNS, no native build, used by the node-discovery adapter), tweetnacl, tweetnacl-util, js-sha512. - Engine is consumed UNCHANGED: the mobile lane is working in the same package and two sessions editing it is the one guaranteed conflict. Engine changes go through the plan's 'Engine asks'. Gate: 24/24 package tests pass from the vendored copy; tsc clean on tsconfig.node.json and tsconfig.web.json; ./node + ./node-discovery subpath exports resolve (NodeTcpTransport, NodeDiscovery). Plan correction in the same commit: two engine asks were withdrawn after checking the real build. Streaming/HTTP transfer (createFileRequestStreaming/Http, createFileCompleteStreaming, verifyFileIntegrity) and ACK (createFileAck) already exist, so large-model transfer is NOT blocked on the other lane — those are host-wiring rules this lane owns instead.
…e the engine directly Two things, both prerequisites for cross-device message sync. 1) CORE SCHEMA — rag_messages.uuid (src/main/database.ts) rag_messages.id is INTEGER AUTOINCREMENT and therefore DEVICE-LOCAL. Live sync keys records by (entity, entityId) across devices, so device A's row 7 and device B's row 7 would look like the SAME message and silently overwrite each other. The autoincrement id stays the local primary key; uuid is the cross-device identity. Includes a JS backfill for existing profiles (SQLite has no uuid()), a UNIQUE index so a replayed remote op upserts instead of duplicating, and uuid on every new insert. Mobile adds the equivalent to its message store under the same names. Verified by src/main/__tests__/rag-message-uuid.dbtest.ts against a REAL legacy profile on disk (4/4): the column appears, every pre-existing row is backfilled with a DISTINCT uuid, uniqueness is enforced, the production writer populates it, and the migration is IDEMPOTENT — rewriting uuids on each launch would orphan the record on every other device. 2) CORRECTION — stop vendoring @offgrid/sync M0 vendored shared/packages/sync into desktop/packages/sync, copying the existing @offgrid/clipboard|design|models|rag convention. shared/docs/DESKTOP_SYNC_INTEGRATION_PLAN.md §1 says explicitly NOT to duplicate this package: reference it directly, as mobile does. Now 'file:../shared/packages/sync'; the copy is removed and the plan doc records the correction. (The other desktop/packages/* copies have already silently drifted from shared/, which is the argument for the direct ref.) Full suite: 377 files / 3084 tests. The renderer integration failures seen while running this are load-dependent flakes, not regressions — a clean tree fails a DIFFERENT test, and all five pass in isolation with these changes applied.
The first CI run of this suite (OGAD 31067953547) measured it honestly: 71 of 75 files
and 243 of 248 cases pass on ubuntu. Four fail for reasons that are the RUNNER, not the
code, each verified from that log rather than guessed:
- multimodal-rag-lifecycle: resources/bin/ffmpeg is a bundled macOS binary. On ubuntu it
exits 1 immediately, so the fixture audio the journey imports is never created.
- image-runtime-reliability: needs a live local engine on its port ("fetch failed /
connect ECONNRESET 127.0.0.1:38119"). CI has no llama-server, and the journey is about
reachability rather than anything the runner can stand up.
- update-check: reads real release history, so with no network it sees an empty list where
it expects 0.0.102.
- clipboard-popup-journey: fails on the runner only, cause not yet diagnosed - named
separately for that reason rather than folded into one of the above.
They stay in the local `npm run test:db`, where they pass on a Mac. Keeping the other 243
is the whole point: they cover database.ts, rag/store.ts, prompt-store and
runtime-residency, which the default project excludes with the note "covered by the tests
in *.dbtest.ts via npm run test:db" - a claim nothing verified until this step existed.
Every exclusion carries its evidence in vitest.db.ci.config.ts and is meant to be deleted
as its cause goes away, rather than being made advisory with continue-on-error, which
would have turned the whole step into a green light that means nothing.
…keeps history
Two Greptile P1s on the restore path, both verified before changing anything.
RESTORED CHUNKS HAD NO EMBEDDINGS. They landed with embedding = NULL, retrieval requires
a non-null one ("WHERE d.enabled = 1 AND c.embedding IS NOT NULL" in rag/store.ts), and
NOTHING ever re-embedded them - the background backfill feeds universal search from
observations, frames and transcripts, never rag_chunks. So a restored knowledge document
sat in its project looking enabled and could never inform an answer, permanently.
The archive carries no vectors (DesktopBackupChunk is content + position), so they are
recomputed on restore, using the same MiniLM the RAG indexer uses. Computed BEFORE the
write transaction, because better-sqlite3 transactions are synchronous and cannot await;
the embedder is injected and defaulted, so a test can supply a deterministic one and a
restore that touches no documents never pulls the model in. A model that will not load
does not fail the restore - those chunks keep their null, exactly as before this change.
AN EXISTING CONVERSATION SKIPPED ITS ARCHIVED MESSAGES. `if (exists) continue` dropped
the whole conversation, so a conversation already here - synced from another device, or
half-restored by an earlier run - silently lost every message only the archive had, while
the restore reported success. The archive has no message id, which is why skipping was
chosen; identity now comes from the content instead (same conversation, role, text and
timestamp IS the same message), so a restore fills gaps without producing a second copy
of everything.
Three regression cases: the vector reaches the row; a restore with no embedder still
lands the document; and a conversation that already exists gains the missing message
without duplicating the one it had. 84 backup tests pass.
…iour CI's DB journeys caught this, which is exactly why that step now exists: 226 of 227 cases passed and the one failure was this assertion expecting `embedding: null` on a restored chunk - the old behaviour, encoded as the expectation. The same test asserts two lines earlier that the restored document is ENABLED, so together they described a document that shows as usable and can never answer: retrieval requires a non-null embedding, and nothing re-embedded a restored chunk (f694a59). Now asserts the SHAPE - a 384-element array of finite numbers - rather than the exact values, which belong to the model rather than to this test. The real MiniLM produced them on the runner, so this also confirms the fix end to end with the actual model, not a deterministic stand-in.
The roundtrip journey asserted a 384-float shape from the REAL embedder, so it described two different outcomes depending on the machine: null where MiniLM cannot load, and a vector where it can. That is not a journey, it is a coin flip - and it is what left CI red while the suite passed locally. The port now takes its embedder as a constructor argument (default: the real one), so the test injects a fixed vector and asserts that vector REACHES the rag_chunks row. That is what the journey is about: a restored, enabled document whose chunk has no embedding can never inform an answer, because retrieval filters on "embedding IS NOT NULL". Also records the verified state of the desktop mesh in the roadmap.
…ing it Playwright has no headless mode for Electron - `headless` governs browsers it launches itself, while an Electron app creates its own BrowserWindows. Linux CI hides that behind xvfb; on a Mac there is no equivalent, so every `npm run test:e2e` (and every pre-push) opened ~25 maximized windows and took the keyboard away from whoever was working. The standing workaround was OFFGRID_SKIP_E2E=1: quiet bought by not running the tests. No window is needed. Playwright drives the webContents, not the screen, and the renderer loads and paints regardless - so a headless run is simply never calling show(), plus app.dock.hide() so the app cannot become frontmost either (the half of the interruption that is not the window). `npm run test:e2e` now sets OFFGRID_E2E_HEADLESS=1, which every spec inherits because each spreads process.env, including the two that launch the packaged app directly. Measured, not assumed: 80 passed headless, and screenshots come out fully rendered at 3024x1670 (a hidden window still paints, so page.screenshot works). Six specs failed in the full headless run; five of them fail on a real display too, and the sixth passes 3/3 headless in isolation, so it was interference between parallel workers rather than visibility. Headless costs nothing. The decision is a pure resolver with its own tests, because the direction that matters is the other one: a real user's app must never resolve to hidden, which would be indistinguishable from failing to launch. OFFGRID_E2E_HEADED=1 wins over headless so a failing spec can still be watched.
…y CI steps that hide them
sonar-project.properties is ignored in Automatic Analysis mode - provable, not assumed: PR #76 reported issues in scripts/, e2e/ and .github/workflows/ci.yml, all three of which that file already excludes. Automatic Analysis reads .sonarcloud.properties instead, and this repo has no CI scan step. The cost was a gate grading the wrong code. Every BUG and VULNERABILITY it reported was in a developer script or in CI YAML - zero in product source - and one BLOCKER in a physical-sync script put new-code reliability at E, so the check failed for reasons no user could encounter. Scripts, e2e and test files are now out of the analysis; they answer to lint, typecheck and the coverage ratchet.
…t just the main one Hiding the main window was half the job. pro opens several windows that call show() and focus() themselves - the clipboard quick-open popup, the tray and CRM notification surfaces, the meeting notice - and core re-focuses the running copy on a second-instance launch, which resilience-single-instance.spec.ts triggers on purpose. So a headless run still stole focus, once per spec that touched any of them, which is what the developer actually noticed. Every window created in a headless run is now non-focusable. That is the technique pro's dictation overlay already uses deliberately (non-focusable + showInactive, so the user's target app keeps the keys); this applies it to all of them, from the one place that knows the launch is headless - so pro needs no changes and a window added later is covered by default. Visibility is deliberately left alone. Popups still appear and their isVisible()-gated logic keeps working, which is what lets the clipboard quick-open journey pass headless - it simply cannot take focus now. The presentation is also resolved ONCE at module scope instead of per call site, because the main window, the Dock tile, second-instance focus and pro's windows disagreeing is exactly how this bug happened. Measured: with the pro and single-instance specs running headless, the frontmost macOS application was only ever Slack and Brave - Off Grid never took the keyboard.
…lete harness
Three pro-tier Devices specs were red in CI and locally both, invisible because the
e2e step is advisory. Two of them were never about the app.
`renders the real Devices screen with live sync status` asserted the text
"LAN + nearby ready" and a heading "Personal mesh". Neither string exists anywhere
in src/, pro/ or shared/packages/ - the first was never shipped and the second is
now "Licensed devices". The screen reports per ROUTE ("LAN: ready", or
listen/advertise/browse states when it is not), so that is what it now asserts. The
spec after it was inheriting the broken screen state rather than failing on its own.
The pairing spec died inside its own setup: it built a ClipboardSyncCoordinator with
no deliveryPersistence, so loadPendingDeliveries threw before the app was touched.
The synthetic peer now keeps deliveries in memory beside its history, standing in
for the peer's disk and none of our logic.
5 of 6 in the file pass headless now. The pairing spec still fails, and for a real
reason: pairing requires an 8-character code from the other device and the synthetic
peer mints none. Recorded in the gaps doc against the standing pairing-harness work
instead of being bodged or deleted.
… Grid AI
Seen on the Windows build, but it was never platform-specific: these strings live
in the core renderer, so every platform showed them. The brand is Off Grid AI
everywhere - window titles, OAuth clients, about screens, badges - and the tier
was being announced as "Off Grid Pro" on the upgrade screen (badge, heading,
platform note) and in onboarding, with the mobile app called "the Off Grid phone
app".
Conversational prose that uses "Off Grid" as the subject ("Off Grid reads your
calendar") is left alone: that reads as the short name in a sentence rather than a
product being misnamed. What changed is only where a PRODUCT or TIER is named.
Windows is an enabled platform now, so "Off Grid AI Pro is macOS-tested today" and "support for this Windows PC will be enabled once it is tested" are simply untrue - and they were the first thing a Windows user read on the upgrade screen. The notice stays per-FEATURE, because that part was already right and is derived from data: proCatalog declares platforms per feature, and several genuinely are macOS-only (capture via ScreenCaptureKit, meetings via the macOS recorder, dictation via its hotkey helper). So the sweeping platform claim is gone and what remains is the honest, narrower one - this feature runs on Mac today, and the license covers Mac, Windows and the phone app up to 5 devices. Not touched: which features declare win32 support. That is a claim about ported code, not copy, and inventing it here would put a feature on a screen that cannot run.
…oes not exist
This is the spec that "gets stuck on the cmd+K search" - and nothing was stuck. It
looked for the placeholder 'Search everything…', which is not a substring of what
the palette actually renders ('Search everything, or jump to a screen…'), so the
locator matched no element and every focus assertion waited out its full timeout
before failing. ~17s per attempt, three attempts with retries, on every run.
Now anchored on the stable half of the string, so a copy tweak to the tail cannot
resurrect this. The spec passes in 1.7s. The palette was always right: it is a
Radix Dialog around cmdk's CommandInput, which takes focus on open.
Also here, because they landed in the same pass:
- the pre-push e2e now runs on the TEST BOX whenever it answers (scripts/e2e-on-box.sh),
falling back to a local headless run only when the box is unreachable (exit 20),
and bringing coverage + screenshots home so the coverage gate still counts them;
- licence copy says "desktop and mobile" rather than enumerating platforms.
Rebranding the badge to "Off Grid AI Pro · Available now" broke the two tour specs that assert that exact string - the upgrade-screen tour and the purchase-link check. A copy change and the tests that depend on it belong in the same pass; I missed these, and the test-box run is what caught them, which is the argument for having that run in the hook at all.
V8 writes the absolute path of the machine that produced the coverage, so every entry came home saying file:///Users/admin/ogad-e2e/... and c8 could map none of it to this checkout. The whole e2e contribution vanished from the coverage gate - measured: 3823 box paths against 69 local ones, and the gate dropped from 72.8% to 63.5% the first time a push used the box. The prefix is now rewritten as the files arrive, which is enough for c8 to resolve them through the bundle sourcemaps: 342 of 342 files in the report map to this checkout afterwards. Moving the run to another machine must not quietly reduce what the gate can see.
…d on the box Without this, checking a single spec meant running it locally "just to check" - which is the exact habit the box exists to remove, and it puts app windows back on the developer's screen. Now: bash scripts/e2e-on-box.sh devices-sync.spec.ts -g "pairs a real peer"
…itself Pairing consumes a licensed mesh seat, so a spec that pairs needs a real entitlement - and the dev target was never seeded, only the packaged one. Two facts made this harder than a file copy, and both are worth writing down: - A licence cache is sealed with macOS safeStorage, and a Keychain item is ACL'd to the application that created it. A licence activated by the SIGNED PACKAGED app is therefore unreadable by `electron .`: it fails to decrypt, the DB key fails with it, setupIPC dies on "file is not a database", and the app reports itself unlicensed while a perfectly valid licence sits on disk. Seeding the packaged profile is actively worse than seeding nothing. - So the fixture has to be created BY the dev build. scripts/seed-e2e-license.mjs does that through the licence IPC - no UI, no clicking - and keeps the resulting profile as the fixture launch.ts seeds from. It retries while activation reports network_unavailable, because that is what activation says when pro's entitlement owner has not registered yet (a 5s readiness wait, not a network fault). Run once per machine: node scripts/seed-e2e-license.mjs
This spec was written against a pairing flow that no longer exists, and every run spent 15s discovering that. Five separate things were stale or missing; each one hid the next, which is why it read as "pairing is broken": - The codes were 'synthetic-pair-code' / 'different-pair-code'. A code is 8 characters from a confusable-free alphabet, so the field rejected both outright - the "mismatch" case was failing VALIDATION, not proving a mismatch is refused. Now it presents the code the Mac is actually showing, read from its status, the way a person reads it off the other screen. - There is no "X wants to pair" heading, no "Incoming pairing code" textbox and no Accept button any more; those strings exist nowhere in the renderer. The host compares the presented code itself, and the dialog that remains is informational with Cancel as its only action. - Pairing is a licensed transaction on BOTH sides: without an entitlement adapter the peer refused its own handshake with entitlement_unavailable. The peer now carries the real-world shape of a joining phone - unlicensed, sponsored by the Mac, so only the import half is implemented and the export half refuses out loud. - The secure-store gate was released before the peer had reached it, so the peer blocked forever in begin() and the attempt died as "Pairing was cancelled" a minute later. It now waits for the gate to exist first. - The clipboard coordinator asks for pairedDeviceIds AND connectedDeviceIds; the harness only answered the second, which surfaced the moment pairing began working. Pairing now completes for real: the app reports paired + connectedIds carrying the peer and the header counts "1 connected", which is what this step asserts. It does NOT assert a device row: that list renders licence-registry devices and discovered devices, and a stub entitlement adapter registers no machine - a real phone does, as part of pairing. Asserting the row would be asserting a side effect the harness deliberately does not have.
Both of these asserted behaviour that was changed on purpose, and both were my own misses - a change and the tests that depend on it belong in the same commit. - UpgradeScreen.windows-notice asserted /macOS-tested/. That claim is gone because Windows is live: telling a Windows user Pro is "macOS-tested today" and will be enabled "once it is tested" was false. It now asserts the honest pair - Windows is live with features arriving one at a time, and the licence covers desktop and mobile - and asserts the old claim is absent. - keygen-personal-mesh-registry asserted that an incomplete machine record REFUSES the whole roster. That refusal is exactly what locked a real licence out of every device, so such a record is now kept as a seat attributed to no device, which the eviction order releases first.
Every push was running all 25 spec files - minutes of wall clock to check a one-line edit, which is how a gate becomes something people skip. CI runs the full suite regardless, so the local gate does not need to. The rule: specs changed and no app code touched -> just those specs. Anything under src/, pro/ or shared/ can break any surface, so that still earns the full run. Explicit arguments always win, and E2E_FULL=1 forces everything.
|
Too many files changed for review (203 files, 100 file limit). Bypass the limit by tagging |
It is a six-minute suite and CI runs it on every PR regardless. Wiring it into every local push made the gate something to work around instead of something to trust - and it put a stream of app launches and minutes of waiting between the developer and a one-line commit. Now opt-in: OFFGRID_E2E_ON_PUSH=1 git push. The box runner it calls is unchanged, so asking for it still runs on the test box rather than the developer's machine.
…ode lies about itself Found while sweeping the four-device lab mesh: the Windows guest advertises as macOS, so the Android lists two macOS devices for one Mac. Records why this is P1 rather than cosmetic - platform gates the Apple-proximity route choice and the model-transfer platform guard, so a mislabelled Windows peer both gets dialled over an Apple-only transport and is allowed to receive a macOS-only model it cannot run.
…till reporting sync running Found sweeping the lab mesh: the .64 packaged app held zero TCP/UDP sockets (no mesh listener, no llama-server, no gateway) while pro:sync:status still answered serviceState:'running'. Records the evidence, why it cannot have been a startup bind failure (the LAN route is required:true, so that path would have aborted setupSyncIPC), and the actionable part - status should derive from the bound listener so it cannot outlive the socket.
…ing the target's own seat Driving the real lab mesh: after putting the Mac's licence on the Android, pairing with that same Mac is refused with 'All slots are in use'. Three tangled defects - it refuses where it documents reclaiming; the '5 of 5' counter counts licence machines while the list renders only local pairings, so the three invisible seats cannot be forgotten as instructed; and the pairing target itself holds one of the five seats, so its own seat blocks pairing with it.
Drops the '2 free / 3+ paid' device cap from the roadmap and docs, and renames the loose 'free tier' wording in the e2e specs and architecture doc to 'unlicensed', which is what OFFGRID_PRO=0 actually simulates.
It was text-green-300 on a transparent background behind a 40%-opacity border - the lightest green in the scale, on no fill at all. Barely readable in dark mode and worse in light, and it is the one action a person needs immediately after activating a licence. Now the same treatment every other primary action in the app uses: solid emerald with white text, a hover state, and the standard active:scale-95. The status line beside it also becomes theme-aware rather than assuming a dark background.
text-green-400 is tailwind's yellowish green (#4ade80), not the brand accent, and on a
light background it is not readable at all. 61 lines across 21 files.
Done by context rather than blind substitution, because one replacement would have made
some cases worse:
- text on the page background -> text-emerald-600 dark:text-emerald-400, because that
background flips with the theme and needs both
- text on surfaces that are dark in BOTH themes (bg-neutral-800/900, bg-green-500/10,
prose-invert) -> text-emerald-400, where the light shade is the legible one and
emerald-600 would be worse
- hover / group-hover -> hover:text-emerald-500, one mid shade that reads either way
without doubling every variant
This is also a brand correction: CLAUDE.md names emerald (#34D399 dark / #059669 light)
as THE accent, and these were off-palette.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



The desktop side of the same story: your Mac joins the Personal Mesh, so chats, files, clipboard and models move between it and your phone over your own network. Plus a portable backup you own, and the coverage plumbing that makes the e2e tour count.
106 commits, 172 files, +10,551 / -1,107. Pairs with desktop-pro#39, which carries the private half.
What this gives you
Chat that reflects the mesh as it happens. A message synced from another device appears when it arrives, not when something else triggers a reload. The streaming reply is published as one observable snapshot, so the window cannot render half a state. Empty synced thinking placeholders stay hidden, and the conversation list shows the last message under each chat.
A backup you can carry. A portable desktop archive engine plus the settings to drive it: your data leaves in a format you own, and comes back.
Model transfers you can verify. Device transfers are registered and symlinked transfer files are rejected outright rather than followed.
Projects that stop losing work. Deleting a project preserves its chats; assigning one refreshes them. Short non-empty documents are indexed instead of silently skipped.
Devices beyond macOS. The Devices surface is available on Windows too, not only macOS. Nested screen history is retained, so going back does what you expect.
Licensing that behaves at the cap. Cached access is verified at launch, activation outcomes are rendered from the shared result rather than re-derived, the stalest seat is replaced when you hit the device cap, and there is a local Pro reset.
Verification
npm run test:coverage, which is the same gate the pre-push hook runs.e2e-screenshots,e2e-coverage); the tour remains advisory while headless-Electron launch stability on the ubuntu runner is the open question, which is recorded indocs/GAPS_BACKLOG.mdalong with the note that it once hid four real spec defects.OFFGRID_E2E_COVERAGEmakes the tour count: 25 specs drive the real app,devices-syncalone stands up a synthetic peer with a real SyncEngine, and none of it reached a coverage report before because Playwright launches Electron as its own process.One
cijobThis repo used to report two checks.
e2ewas a second parallel job whose first nine steps were byte-identical duplicates ofverify: the same pro checkout with its main fallback, the same shared monorepo provisioning, a secondnpm ci, all to feed a second runner. Those are deleted, and typecheck, tests with the coverage floor, dependency boundaries, lint and the e2e tour now report as onecicheck.The tour runs after the unit gates instead of beside them, so a PR takes about 7 minutes longer to go green. An earlier attempt at this was reverted for blowing a "25-minute job cap" (PR #68); that cap was this workflow's own
timeout-minutes, so it is raised to 50 deliberately rather than worked around.Note for anyone with branch protection: the check renames from
verifytoci.Tests worth calling out
approval-lifecycle.tswent from 0% coverage to covered. Its only tests were.dbtest.tsfiles, which the default vitest project excludes because they need better-sqlite3 rebuilt for the node ABI, and which CI never runs at all, so the highest-stakes logic in the "act" pillar was both measured as untested and actually untested in CI. It now runs against a real in-memory SQLite with the real approval queue, audit log, execution claim and preference store; only the MCP connector call is stood in for, because that is a separate process reaching a third-party API and the only way to observe whether it actually acted:The desktop
macos-proximitysuite now stands up a real file at the bin resolver's path instead of mocking../lib/bin-resolution, verified by hiding the gitignored binary, which is the CI condition.Known gaps, recorded not hidden
docs/GAPS_BACKLOG.md, notably: 103 DB tests never run in CI (excluded from the default project, notest:dbstep), which is why a file with tests measured as 0%; and the ChatScreen journeys left uncovered by deleting a 155-case suite that stubbed fourteen of our own modules, with the measured 8-point statement drop and the four named journeys that now want rendered tests.Replaces #75, which GitHub closed when the branch was renamed to
release/sync-cross-platform.Greptile Summary
The PR adds cross-device synchronization, portable backup and restore, desktop presentation updates, licensing changes, and expanded unit, database, and Electron coverage.
Confidence Score: 4/5
The PR does not yet appear safe to merge because additive restore can still silently omit distinct archived messages that collide under its content-derived identity.
The reply says the archived-message omission was fixed by deriving identity from conversation, role, text, and timestamp, but the current implementation compares a second-precision timestamp and therefore treats two legitimate identical messages created in the same second as one, leaving the original incomplete-history failure reachable.
Files Needing Attention: src/main/backup/data-port.ts
Important Files Changed
Sequence Diagram
Reviews (3): Last reviewed commit: "test(e2e): stop the Devices specs failin..." | Re-trigger Greptile