A cross-platform plan covering everything still needed to ship a v1.0 release across macOS, Windows, and Linux. Synthesized from the three per-platform the-plan.md / next-steps.md files and the live API review against interlinedlist.com/help/api.
Last updated: 2026-06-23 (after Phase 4 landed)
Status: Phase 4 complete on all three platforms; CI green on main.
| Platform | Phase model | Done | Next |
|---|---|---|---|
| macOS | 7 Phases | 1–4 ✓ | Phase 5 (Preferences + Login Item) |
| Windows | 8 Phases | 1–4 ✓ | Phase 5 (Conflict Resolution) |
| Linux | 7 Milestones | M1–M3 ✓, M4 partial | Finish M4, then M5 (Offline Queue) |
CI status on CompositeCode/interlinedlist-synchronization: all three platform workflows green; .deb artifact builds cleanly. No v* release tag has been cut yet; release.yml has never run.
The three client implementations diverge from the live API in identical ways. Fix once across all three before extending features.
| Concern | API reality | Action |
|---|---|---|
| Update verb | PATCH /api/documents/[id] |
Change PUT → PATCH on all three clients |
| Body field name | content |
macOS only: rename body → content in DocumentDTO |
| Folder model | Folder { id, name, parentId, createdAt } + Document.folderId + relativePath |
Add to macOS + Linux (Windows already has model, needs CRUD client methods) |
| Delta sync | GET /api/documents/sync?lastSyncAt=... returns { syncedAt, folders[], documents[] } with deleted: true tombstones |
Adopt on all three to fix tombstone handling natively |
| Auth | Most endpoints session-only; /sync accepts Bearer |
Verify Bearer works on POST/PATCH/DELETE — needs integration test against live API |
- Auth contract — does the
POST /api/auth/loginBearer token work on non-/syncendpoints? This is the single highest-impact unknown. relativePathsemantics — server-suggested local filename, historical artifact, or path-within-folder?- Folder scope for Phase 5 — bundle with conflict resolution, or ship flat-first and add folders as Phase 5.5?
PreferencesView(SwiftUI) wired toPreferencesManager:- Sync folder picker (
NSOpenPanel, store security-scoped bookmark) - Poll interval slider (5–300 s)
- Conflict strategy display (remote-wins + conflict-copy, read-only for v1)
- Notifications enable/disable toggle (already in
PreferencesManager, just needs UI) - Sign-out button
- Sync folder picker (
SMAppServicefor launch-at-login toggle- Hot-reload of sync interval when preference changes (currently set once at engine construction)
- Tests:
PreferencesViewModelTests,LaunchAgentManagerTests
ConflictResolverpolicy: remote-wins → writefilename.conflict-YYYYMMDD-HHMMSS.mdfor local divergence- Per-document
SemaphoreSlim-equivalent (Swift actor isolation) so push and pull serialize per file, not globally - Error surfacing:
- Auth expired → notification + tray badge → reopen preferences
- Network offline → pause loop, show "Offline" in menu
- Rate-limited (HTTP 429) → exponential backoff with jitter
- Tests:
ConflictResolverTests(covers all five conflict permutations),RetryPolicyTests
make-app.shalready builds.app; add:- Code signing (Developer ID Application cert from Apple Developer)
- Notarization via
notarytool(Apple ID + app-specific password as repo secrets:APPLE_ID,APPLE_APP_SPECIFIC_PASSWORD,APPLE_TEAM_ID) - Stapling
- Optionally
.pkginstaller withproductbuild - Update
release.ymlmacOS job to sign + notarize before uploading
- Mirror macOS Phase 6 policy: remote-wins +
.conflict-YYYYMMDD-HHMMSS.mdcopy - Replace the single
SemaphoreSliminSyncEnginewith per-documentSemaphoreSlimso push/pull don't fully serialize - Conflict detection during push: hash check +
updatedAtcomparison before PATCH - Tests:
ConflictResolverTests, extendSyncEngineTestswith conflict permutations
- Windows App SDK toasts via
AppNotificationManagerfor: sync completion (changes only), sync failure, conflict copy - Toast actions: "Open Folder", "Retry"
- Auth-expired badge on tray icon
- Network change detection via
NetworkInformation.NetworkStatusChanged→ pause/resume - Tests:
NotificationManagerTests(mock toast manager),NetworkMonitorTests
- WPF
SettingsWindow.xaml:- Sync folder path + browse (
FolderBrowserDialog) - Poll interval slider
- Auto-start toggle (write to
HKCU\Software\Microsoft\Windows\CurrentVersion\Run) - Notifications toggles
- Sign-out button
- "Open sync folder" / "Open log folder" shortcuts
- Version + log path display
- Sync folder path + browse (
OnboardingWindow.xamlfor first-run sign-in + folder selection- Tests: WPF view-model tests (no UI thread dependencies)
- Wire
InterlinedSync.PackageMSIX project into CI:dotnet build -c Releaseon.Packageproject produces.msix/.msixbundle- Code signing certificate (configure as
WINDOWS_CERT_PFX_BASE64+WINDOWS_CERT_PASSWORDrepo secrets) - Add Windows job to
release.ymlfor tagged builds
- Optionally: framework-dependent vs self-contained publish trade-off (self-contained = bigger zip, no .NET runtime install required)
From the Linux engineer's report:
- Folder picker result needs to be persisted on settings window close (currently shows in UI only)
- Add "Local wins" option to
ConflictResolutionenum (currently onlyRemoteWinsandConflictCopy) - Single window-level Apply/Revert instead of per-panel Save rows
- Live
KeyringSecretStoreintegration test (currently#[ignore]'d; needs a Linux runner with D-Bus + GNOME Keyring)
- New
queue-storecrate (or extendstate-store) withpending_opstable:(id, op_kind, doc_id, payload_json, queued_at, retry_count) SyncEngine::run()drains the queue on startup and on reconnect- NetworkManager D-Bus subscription via
zbus→ trigger drain when state becomesCONNECTED_GLOBAL - Tests: queue insert/drain/retry; D-Bus listener mock
.debbuilds (verified in CI) but maintainer scripts are incomplete:postinst:sysctl --system(for the sysctl drop-in) +apparmor_parser -r /etc/apparmor.d/usr.bin.interlinedlist-syncprerm: reverse both
- Add to
Cargo.toml's[package.metadata.deb] - PPA setup (separate from this work) — document the upload process
snapcraft.yamlwithcore22base,confinement: classic(file access required), interfaces:home,network,desktop,password-manager-service- CI job to build snap on tagged releases
- Snap Store account + review process (out of scope for v1 but document)
All three platforms have only unit tests today. Live-API integration tests are needed before v1.
- GitHub secrets on
CompositeCode/interlinedlist-synchronization:INTERLINEDLIST_EMAILINTERLINEDLIST_PASSWORDINTERLINEDLIST_API_BASE_URL
- Workflow updates — each CI workflow's test step gains:
env: INTERLINEDLIST_EMAIL: ${{ secrets.INTERLINEDLIST_EMAIL }} INTERLINEDLIST_PASSWORD: ${{ secrets.INTERLINEDLIST_PASSWORD }} INTERLINEDLIST_API_BASE_URL: ${{ secrets.INTERLINEDLIST_API_BASE_URL }}
- Local convention —
.envalready in place + gitignored; each platform's test harness reads from env vars at startup - Gating — integration tests skip gracefully when env vars are absent (fork PRs, etc.)
- Branch policy — recommend: run on PR-to-
main+ manualworkflow_dispatch, not every push (avoids hammering live API)
| Platform | Test target | Convention |
|---|---|---|
| macOS | InterlinedSyncIntegrationTests SPM test target |
XCTSkipIf(ProcessInfo.processInfo.environment["INTERLINEDLIST_EMAIL"] == nil) |
| Windows | InterlinedSync.IntegrationTests (already exists; currently empty scaffolding) |
xUnit Skip="..." attribute or IConditionalTheory |
| Linux | tests/integration/ directory; #[ignore] by default unless env vars present |
cargo test --ignored runs them |
- Login → bearer token returned
- Token works on
GET /api/documents,POST,PATCH,DELETE - Token works on
GET /api/documents/folders+ CRUD /api/documents/syncdelta returns expected shape- Round-trip: create local file → verify upload → fetch from server → matches
- Round-trip: create on server → verify download → matches
- Conflict scenario: modify both sides → verify conflict copy created
- Cleanup: integration tests must tear down any docs/folders they create
The application is a menu-bar / tray sync client. UX details below apply uniformly across all three OSes (with platform-idiomatic adjustments).
| State | Icon | Trigger |
|---|---|---|
| Idle | static cloud or circular-arrow icon | no activity, last sync succeeded |
| Syncing | animated rotation or spinner overlay | active push/pull |
| Paused | pause overlay | user paused via menu |
| Error | warning badge | last sync failed; details in tooltip |
| Conflict | conflict badge | one or more conflict copies created since last user view |
| Offline | offline indicator | network lost; queue building locally |
| Auth needed | exclamation badge | token expired/invalid; click → reopen sign-in |
Implementation status:
- macOS — Phase 4 landed idle / syncing / paused / error via SF Symbols. Conflict / offline / auth-needed states still need wiring.
- Windows — partial (icon placeholder); state-driven badging is Phase 6 work.
- Linux —
ksnitray supports state changes; needs wiring throughSyncStatusenum updates.
┌────────────────────────────────┐
│ Signed in as user@example.com │ ← header, not clickable
│ Last synced 5 minutes ago │ ← live status
│ ── separator ── │
│ Sync Now │ ← triggers immediate poll
│ Pause / Resume │ ← toggles
│ ── separator ── │
│ Open Sync Folder │ ← Finder/Explorer/Files at root
│ Open in InterlinedList │ ← https://interlinedlist.com
│ ── separator ── │
│ Preferences… │
│ Quit │
└────────────────────────────────┘
When in Error / Conflict / Auth-needed state, prepend a contextual item:
- Error: "Last sync failed — click for details" → opens log viewer
- Conflict: "N conflicts since last sync" → opens sync folder filtered to
.conflict-*.md - Auth-needed: "Sign in required" → opens onboarding sheet
| Panel | Controls |
|---|---|
| General | Sync folder path + browse · Launch at startup · Sync interval (5–300 s slider) |
| Account | "Signed in as …" · Sign out button · Open InterlinedList in browser |
| Notifications | Enable notifications · Sync completion · Sync errors · Conflict copies |
| Conflict Resolution | Strategy: Remote wins + conflict copy (read-only for v1) · Show conflict log |
| Advanced | Log file path (read-only, with "Open Log Folder" button) · Reset state (with confirmation) · About / version |
- Welcome screen — brand mark + one-line value prop + "Get Started" button
- Sign-in — email + password →
POST /api/auth/login→ store token in platform keychain - Folder selection — default to
~/InterlinedList Sync(or platform equivalent); browse option - Initial sync progress — full document list pulled; progress bar showing N of M files
- Done — "Your documents are syncing. Find them at /path/to/folder. The app lives in your menu bar." → "Open Folder" + "Got It" buttons
Status:
- macOS has
OnboardingViewbut only steps 1 + 2 + 3 are present. Steps 4 + 5 are TODO. - Windows —
OnboardingWindowis planned for Phase 7, not yet built. - Linux — currently CLI-only (
--loginflag). Needs a GTK onboarding window or accepted as power-user-CLI convention.
| Event | Type | Body | Actions |
|---|---|---|---|
| Sync completed (changes only) | Info | "Synced 3 documents" | Open Folder |
| Sync failed | Error | "Couldn't reach InterlinedList. Will retry in 30 s." | Retry, Open Log |
| Conflict copy created | Warning | "Conflict in 'Document Name.md' — copy saved." | Open File |
| Auth expired | Error | "Sign in to continue syncing." | Sign In |
| Initial sync complete | Info | "Initial sync done — N documents downloaded." | Open Folder |
Toggleable per category in Preferences → Notifications.
- Server folder hierarchy mirrors to local subdirectories under the sync root
- Folder rename on server → local rename (atomic where possible)
- Document moved between folders on server → local file moved
- User creates local folder → creates server folder via
POST /api/documents/folders - Conflict: local folder name collision with server folder created elsewhere → rename with
-conflict-YYYYMMDD-HHMMSSsuffix - Empty folders on server: keep represented locally (don't delete just because empty)
| Condition | Behavior |
|---|---|
| Network offline | Pause sync, show offline icon, queue local changes, resume on reconnect |
| Auth expired | Tray badge + notification, open preferences sign-in |
| Server rate-limited (HTTP 429) | Exponential backoff with Retry-After header honored |
| Disk full | Pause sync, surface error notification, halt pull until resolved |
| Sync folder missing/moved | Pause sync, open preferences to re-pick folder |
| Two clients editing same doc | Per remote-wins + conflict-copy policy |
| Document deleted on server | Move local file to trash (not permanent delete) — needs design confirmation |
README.mdat repo root — currently minimal; should describe the three native apps, link to per-platform READMEs, link to releases- Per-platform
README.mdfiles exist but need: install instructions for end users, troubleshooting section, where logs live CONTRIBUTING.md— does not exist- GitHub Pages site (the
docs-writeragent's "documentation branch" target) — never set up - API contract reference (snapshot of
/help/apifindings to avoid live-doc drift)
Use this matrix when delegating. Each agent gets a self-contained brief and operates on disjoint directories so they can run in parallel.
| Workstream | Agent | Scope |
|---|---|---|
| macOS Phase 5–7 | swift-engineer (implementation) + engineering-macos-agent (signing/notarization in Phase 7) |
macos/ only |
| Windows Phase 5–8 | engineering-windows-agent |
windows/ only |
| Linux M4 polish + M5–M7 | engineering-linux-ubuntu-agent |
linux-ubuntu/ only |
Rule of thumb: Phase 5 on all three can run in parallel (different platforms, no cross-cutting dependencies). API alignment work (§2) should be done first as one batched commit per platform — also parallel.
| Workstream | Agent | When to invoke |
|---|---|---|
| API alignment audit (§2) | general-purpose to first inventory the divergences in detail, then per-platform engineers to apply fixes |
Before Phase 5 begins |
| Integration test scaffolding (§4) | per-platform engineers, after §2 alignment lands | After API alignment |
| GitHub secrets setup + workflow wiring (§4.1) | direct execution (not delegated — small focused diff) | Before integration tests |
| UX design pass for Preferences / Onboarding mocks (§5.3, §5.4) | general-purpose first if you want low-fidelity wireframe descriptions; then per-platform engineers for implementation |
Before Phase 5 / 7 implementation |
| Documentation (§6) | docs-writer |
Parallel with implementation; runs against documentation branch |
| Folder-support cross-platform design (§2 follow-up) | general-purpose to draft a shared design doc, then per-platform engineers |
Before folder implementation begins |
To keep iteration tight, batch like this:
Batch A (immediate, before Phase 5) — three parallel agents:
swift-engineer: macOS API alignment (PUT→PATCH, body→content, prepFoldermodel)engineering-windows-agent: Windows API alignment (PUT→PATCH, wireFolderCRUD, swap to/syncendpoint)engineering-linux-ubuntu-agent: Linux API alignment (PUT→PATCH, addFoldermodel + CRUD, swap to/syncendpoint)
Batch B (immediate, in parallel with Batch A) — direct:
- GitHub secrets + workflow env-var wiring for all three workflows (small, no need to delegate)
Batch C (Phase 5 proper) — three parallel agents after Batch A lands:
swift-engineer: macOS Phase 5 (Preferences + Login Item)engineering-windows-agent: Windows Phase 5 (Conflict Resolution)engineering-linux-ubuntu-agent: Linux finish-M4 + M5 (Offline Queue)
Batch D (Phase 6, after Batch C) — three parallel agents:
- macOS Phase 6, Windows Phase 6, Linux M6 (.deb maintainer scripts)
Batch E (Phase 7+) — three parallel agents:
- macOS Phase 7 (signing/notarization), Windows Phase 7+8 (Settings UI + MSIX), Linux M7 (Snap)
Batch F (release):
- Tag
v0.1.0-rc1, runrelease.yml, smoke-test installers, iterate
Every sub-agent brief should explicitly:
- State the scope and which directory(ies) they may modify
- Cite the relevant section of this gap-work plan
- Require tests alongside every change
- Require
swift test/dotnet test/cargo test --workspacegreen before reporting done - Cap the report at ~300 words
A pragmatic critical path from "Phase 4 done" to "v1.0 release":
- Confirm answers to §2 open questions (auth contract, relativePath, folder scope) — user input required
- Batch B — GitHub secrets + workflow wiring (~15 min, direct)
- Batch A — Cross-platform API alignment (3 parallel agents, ~1 session)
- Integration test scaffolding — per-platform, parallel (~1 session)
- Cut
v0.1.0-alphatag — first installer-grade release, internally testable - Batch C — Phase 5 across the board (~1 session)
- Folder support implementation (if §2 scope decision is "include in Phase 5") — likely ~1–2 sessions
- Batch D — Phase 6 across the board (~1 session)
- Batch E — Phase 7+ packaging/signing (~1–2 sessions; signing cert procurement may bottleneck)
- Documentation pass (parallel with implementation throughout)
- Cut
v1.0.0tag → publish public release
Documented here to keep scope contained:
- Multi-account support
- Selective sync (sync only certain folders)
- Per-folder pause/resume
- iOS / iPadOS / Android clients
- Browser extension
- Local search across synced documents
- Conflict-resolution strategy beyond remote-wins + conflict-copy (e.g., three-way merge, user-chosen winner)
- End-to-end encryption of document content
- Offline editing of documents (the app syncs, it doesn't edit)