Conversation
…FollowCounts domain model Decision 0003 (docs/decisions/0003-kit-import-policy.md): App-layer files must import InterlinedKit explicitly; InterlinedDomain re-exports nothing. Resolves Wave 2 deviation #1 by introducing the missing FollowCounts domain model and removing the kit import from the three Social views. InterlinedDomain — M2 write surface on MessagesService: - create(body:parentId:tags:visibility:pushedMessageId:) - reply(to:body:tags:visibility:) - repost(_:commentary:visibility:) - update(messageId:body:tags:visibility:) - delete(messageId:) - dig(messageId:) / undig(messageId:) All wrap the existing Wave 1 InterlinedKit.Messages builders. Cross-post / scheduling / media fields stay deferred to M6 per the doc TODOs. MessageStore gains a remove(id:) method with a no-op default so the out-of-package SwiftDataMessageStore conformance compiles unchanged; InMemoryMessageStore implements concrete eviction across by-id and timeline indexes so delete(messageId:) leaves no tombstoned cache. Tests: InterlinedDomain 76 → 99 (BDD-named quartet — happy/invalid/ failure/empty — for each new write method, plus the FollowCounts mapper and the new InMemoryMessageStore.remove). InterlinedPersistence 13/13 unchanged. InterlinedKit 174/174 unchanged (no kit sources touched). xcodebuild -scheme InterlinedList -destination 'platform=macOS' build succeeds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- README.md with CI badges (main + dev), Swift/macOS/Xcode, repo-status - CI workflow: app job now runs xcodebuild test + uploads unsigned .app artifact (14-day retention) - Contract-tests workflow: separate from CI, manual + nightly schedule, gated on INTERLINEDLIST_EMAIL / INTERLINEDLIST_PASSWORD secrets - Dependabot for GitHub Actions (no SPM ecosystem yet) - PR template + bug / feature issue templates + contact links - API-backend-prompts-to-build.md: P1/P2/P3 asks for the InterlinedList backend team, derived from spike + decisions (public profile, watcher roles, user lookup, cross-post envelope, rate limits, etc.) - NEXT-WORK.md: NW-1 (watcher invite flow) parked pending API lookup Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
+ InterlinedPersistence SwiftData lists cache
InterlinedDomain
- Models: OwnedList, OwnedListsPage, ListSchema, SchemaField,
SchemaFieldType {text,number,boolean,date,url,email}, ListConnection,
ListWatcher, WatcherStatus, WatcherRole {owner,editor,viewer,.other},
GitHubListSource, OwnedListMappers
- Schema/SchemaDSL.swift: parse/serialize with typed SchemaDSLError;
property-style tests (round-trip, every type, whitespace,
duplicate/missing, trailing comma)
- ListsService write surface: myLists / detail / create / update /
delete / schema / updateSchema / refresh / row CRUD / watchers /
connections — every write routed through requireListManagement()
consulting EntitlementsService.canManageLists, throwing
ListsError.subscriberRequired before any HTTP call (defensive
gating; permissive default holds through M6)
- EntitlementsService gains canManageLists +
init(customerStatus:canManageLists:) test seam
- ListsStore cache port
InterlinedPersistence
- @model records: ListRecord, ListsPageRecord, ListSchemaRecord,
SchemaFieldRecord, ListRowRecord, ListConnectionRecord,
ListWatcherRecord
- RowDataCodec: JSON blob codec for dynamic [String: ListCellValue]
rows (keeps persistence kit-independent)
- SwiftDataListsStore actor: inMemory() + onDisk(at:) factories,
cascading removeList (rows/schema/watchers/connections/page index)
- NullListsStore no-op for hostile boot conditions
Tests: +82 domain (181 passing), +17 persistence (30 passing); kit
unchanged at 174. xcodebuild build green. Decision 0003 holds.
21 Lists endpoint rows now consumed at the domain-service layer;
flip to ☑ pending App-layer consumption in Wave 4.3 per the Wave 1
deviation-6 rule.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Views, tests, sidebar router, and menu commands closing out the M3 Lists App-layer slice (view models + ListsEventBus + AppEnvironment wiring landed in 461e7df / 155c955). Views - OwnedListsRootView — NavigationSplitView, sidebar disclosure tree honoring parentID, toolbar (New List, Refresh, Edit Schema, Share, Connections), context-menu delete with .confirmationDialog - NewListSheetView — title/description/schema-DSL/parent/visibility + GitHub-source fields (surface-only pending API-backend-prompts 2.3 write-side companion) - SchemaEditorView — per-field form builder: name/type/nullable, .onMove reorder, validation, read-only for non-owners - ListRowsView + RowInspectorView — typed cells per SchemaFieldType, cards/list toggle, optimistic CRUD with snapshot rollback - WatchersView — role editor only (NW-1 infobox), optimistic role change and remove - ListConnectionsView — SwiftUI Canvas + drag-to-move, drag-to-add-edge, tap-to-remove-edge. Deterministic radial v1 layout with TODO(M3.x) marker for force-directed upgrade. No AppKit. - ListsSidebarRouter — ownership gating: signed-in → OwnedListsRootView, signed-out → M1 ListsBrowserView (preserved) Menu + navigation - ListMenuCommands — Lists menu, "New List" on ⇧⌘N (avoids ⌘N collision with ComposeCommands) - MainWindowView — .lists routes through ListsSidebarRouter - ListDetailView/VM — "Save to my lists" hook on public-list detail (metadata-only v1 pending API-backend-prompts 2.3a clone endpoint) Tests (+62 → 106 total App tests passing) - StubListsService test double - OwnedListsViewModelTests (14), NewListViewModelTests (4), SchemaEditorViewModelTests (10), ListRowsViewModelTests (14), WatchersViewModelTests (9), ListConnectionsViewModelTests (11) Gate - xcodebuild test → 106/106 passing - swift test (Kit/Domain/Persistence) → 174/181/30 unchanged - grep "import InterlinedKit" App/Features App/Navigation App/MenuCommands → empty (decision 0003 holds) - grep "import AppKit" App/ → empty (SwiftUI-only constraint holds) Backend prompts updated (API-backend-prompts-to-build.md) - P2 item 2.3a: public list clone endpoint (unblocks full "Save to my lists" beyond metadata-only) - Companion to item 2.3: POST /api/lists needs gitHubSource block on create so the New List sheet's GitHub fields wire through Known follow-ups in agent report - Dynamic-column SwiftUI Table deferred (needs macOS 14.4 deployment) - Connections graph radial v1; force-directed upgrade flagged - In-place rename UX held back to polish slice Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…status api-coverage.md - 15 Lists rows ◐⁴ → ☑: DELETE /lists/[id], schema (GET/PUT), refresh, data (GET/POST), data/[rowId] (PATCH/DELETE), watchers/me, watchers/users, watchers/[userId] (PUT/DELETE), connections (GET/POST/DELETE) - 4 rows held at ◐⁴ via new footnote 9: GET/PUT /lists/[id], GET /lists/[id]/data/[rowId], GET /lists/[id]/watchers — reachable via ListsService but not exercised by a tested view model this wave - GET /lists and POST /lists held at ◐⁴ pending M3 polish-slice quartet pass at the view-model layer - Math: Implemented 92/98 unchanged; Tested fully 20 → 35; Tested partial 71 → 56; Untested 7 unchanged - 2026-06-23 Update history entry; no other footnotes touched progress.md - Full Wave 4 section appended (M3 Lists, no new decisions, 4.1 + 4.3 summaries with commit refs, gate result, per-suite test table, coverage delta, 9-item deviations + follow-ups including the secrets-set / contract-tests-inert-until-dev→main note) user/feature-status.md - M2 and M3 flipped to "Shipped." - Removed obsolete "Inline message actions arrive with M2" bullet - Added 4 user-facing M3 limit bullets: watcher invites pending API lookup endpoint, deterministic radial graph layout, manual GitHub refresh, metadata-only Save-to-my-lists Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
+ Decision 0004 (Markdown library: Textual + macOS 15) InterlinedDomain - Models: Document, FolderNode, DocumentSyncEvent, DocumentChange, DocumentMappers - Caching: DocumentStore port - Imaging: ImagePrep — passthrough → 1200px downscale → lossless re-encode (HEIC then PNG) → JPEG quality ladder (0.9 → 0.5); pure CoreGraphics/ImageIO, no AppKit - Services: DocumentsService (full CRUD + folders + image upload + sync passthroughs) + DocumentSyncTransport seam - Tests: +63 (DocumentMappers 18, ImagePrep 8, DocumentsService 32, Fixtures expanded); 244 total passing InterlinedPersistence - Schema: DocumentRecord (with localEditedAt dirty bit), FolderRecord, OutboxEntryRecord, SyncStateRecord - Mapping: DocumentRecordMapping, DocumentChangeCodec (JSON blob for outbox payloads) - Stores: SwiftDataDocumentStore actor (in-memory + on-disk factories, folder-cascade delete, outbox FIFO) - Sync: DocumentSyncEngine actor — pull delta → server-wins conflict resolution (local preserved as "<id>-localcopy-<UUID>") → outbox push → cursor advance. AsyncStream<DocumentSyncEvent> with ordered conflictResolved → deltaApplied → pushed events; bufferingNewest(64) - Package.swift: added InterlinedKit dep so the engine can populate DocumentsError.syncFailed(underlying: APIError) - Tests: +40 incl. 50-trial randomized soak in DocumentSyncEngineTests; 70 total passing Decision 0004 (docs/decisions/) - Markdown library: gonzalezreal/textual chosen as the App target's first third-party SPM dependency - Disqualifications: MarkdownUI (maintenance mode), MarkdownView (transitive Highlightr import AppKit), Splash (stale), swift-markdown (parser only, no renderer) - Required corollary: bump macOS deployment 14 → 15 (Textual platform floor). Sequoia is 2 years old at this point; aligns with SwiftUI-only preference recorded in memory - Wave 5.3 owns the implementation: bump pbxproj + three Package.swift manifests + add Textual SPM dep + update README badges + feature-status Gate - xcodebuild test → 106/106 AppTests passing (TEST SUCCEEDED) - swift test Kit/Domain/Persistence → 174/244/70 - App-target grep "import InterlinedKit" → only doc comments (3 false positives in App/Features/Social affirming decision 0003); zero real - grep "import AppKit" in Packages/ → empty 14 Documents & Sync endpoint rows now consumed end-to-end via Domain; ready for Wave 4-style flip in the docs gate after Wave 5.3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Infrastructure (Decision 0004) - macOS deployment target 14 → 15 across all three Package.swift manifests, all 6 pbxproj build configurations, and Info.plist (LSMinimumSystemVersion) - Added gonzalezreal/textual @ 0.5.0 (.upToNextMajor) as the App target's first third-party SPM dep; transitive swiftui-math + swift-concurrency-extras (all pure SwiftUI, no AppKit reach) Composition root + lifecycle - AppEnvironment exposes documentsService, documentSyncEngine, documentSyncEvents stream - KitDocumentSyncTransport wraps APIClient for the engine - InterlinedListApp on-launch .task fires syncNow() detached after currentUserStore.restore() (errors swallowed) Documents feature (App/Features/Documents/) - DocumentsRootView — three-column NavigationSplitView with toolbar (New Document, Sync Now, status indicator) - DocumentsSidebarView + FolderTreeViewModel — disclosure tree, create/rename/delete folders with optimistic rollback - DocumentsListView + DocumentsListViewModel — list per folder, CRUD + deltaApplied event handling, pagination heuristic - DocumentEditorView + DocumentEditorViewModel — vanilla SwiftUI TextEditor for editing, Textual.StructuredText(markdown:) for preview, 1.5s debounced auto-save (injectable), drag-and-drop + .fileImporter image upload routed through DocumentsService → ImagePrep - ConflictBannerView — banner surfaces conflictResolved events matching the open document; preserved-as copy reachable via "Open local copy" action - SyncStatusView + SyncStatusViewModel — Idle/Syncing/LastSynced/ Failed state machine, manual sync button Menu commands - DocumentsMenuCommands (⌥⌘N New Document, ⌥⌘S Sync Now) posts NotificationCenter events the root view subscribes to Navigation - MainWindowView .documents now routes to DocumentsRootView Docs - README.md: macOS badge 14+ → 15+; Building requirements bumped; M3/M4 milestone rows flipped to Shipped (the user previously bumped M3 in the table; this commit picks up M4) - docs/user/feature-status.md: M4 → Shipped; three new "Limits" bullets (manual + on-app-open sync, image-resize behavior, conflict banner UX) Gate - xcodebuild test → 150/150 passing (106 → 150, +44) - swift test Kit/Domain/Persistence → 174/244/70 unchanged - xcodebuild build → SUCCEEDED on macOS 15 deployment target - xcodebuild -resolvePackageDependencies → textual 0.5.0 resolved - All 4 verification greps empty (AppKit, decision 0003, macOS 14 in pbxproj, macOS 14 in Package.swift) Tests (+44) - FolderTreeViewModelTests (11), DocumentsListViewModelTests (12), DocumentEditorViewModelTests (10), SyncStatusViewModelTests (5), ConflictBannerViewModelTests (6) - StubDocumentsService support double 14 Documents & Sync endpoint rows now consumed end-to-end via App view models; ready for the Phase 5 docs gate. Known follow-ups (agent report) - Markdown toolbar inserts at end-of-buffer not cursor (TextSelection is macOS 26+; SwiftUI TextEditor on macOS 15 doesn't expose it) - Drag-drop accepts Data only; URL.self branch is a v1.x add - "Open local copy" silently fails if preserved doc is in a different folder (sync engine should emit folderId — backend ask candidate, not added yet) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feature-status, P3.7 backend ask api-coverage.md - 12 Documents & Sync rows ◐⁴ → ☑: GET/POST /documents/sync, GET /documents, POST/PATCH/DELETE /documents/[id], image upload, GET/POST /documents/folders, PATCH/DELETE /documents/folders/[id], GET /documents/folders/[id]/documents - 2 rows held at ◐⁴ via new footnote 10: GET /documents/[id] and GET /documents/folders/[id] — reachable via DocumentsService but the Wave 5.3 view models open documents from the list payload rather than re-reading by id - Math: Implemented 92/98 unchanged; Tested fully 35 → 47 (+12); Tested partial 56 → 44 (-12); Untested 7 unchanged - 2026-06-23 Wave 5 Update history entry progress.md - Full Wave 5 section appended in Wave 4 voice: Decision 0004 summary, 5.1 + 5.3 slice writeups with commit refs (daf1eef + babb6d2), gate result, per-suite test table (174/244/70/150 = 638 total), coverage delta, 8 deviations + follow-ups user/feature-status.md - Verified Wave 5.3's M4 → Shipped flip - Added one new Limits bullet for the cross-folder "Open local copy" caveat (links to the engineering follow-up) API-backend-prompts-to-build.md - Added P3.7: sync delta needs to include folderId on changed documents so the macOS conflict-banner "Open local copy" action can succeed across folder boundaries Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Live probe on 2026-06-24 against interlinedlist.com pinned the actual
shape of every follow-list endpoint. The Wave 1 kit had four typed-
wrong endpoints that worked only because synthetic tests fed the wrong
shape too — real API calls would have failed to decode.
InterlinedKit
- FollowUserDTO: rename avatarUrl → avatar (live key); add followId,
createdAt, status (live API includes them on follower/following rows)
- FollowMutualCountsDTO replaces FollowMutualDTO — the endpoint returns
{ mutualFollowers, mutualFollowing } counts, NOT a list of users
- FollowRequestsResponse { requests: [...] } for /api/follow/requests
(no pagination today; downgrade ask 2.1 to a P3 follow-up)
- Follow.followers / Follow.following → Request<Paginated<FollowUserDTO>>
with paginationKey "followers" / "following"; added limit/offset/status
query parameters
- Follow.mutual → Request<FollowMutualCountsDTO>
- Follow.requests → Request<FollowRequestsResponse> (drops the wrong
Paginated<FollowRequestDTO> shape)
InterlinedDomain
- UserSummary.init(from: FollowUserDTO): consume new avatar field name
- UsersPage.init(from: Paginated<FollowUserDTO>): hasMore + nextOffset
derived from pagination block
- SocialService.followers/following: pass limit/offset query params,
consume via sendRaw + PaginatedDecoder (same pattern as MessagesService)
- API-backend-prompts-to-build.md 2.1: marked resolved on the headline,
downgraded the pagination-of-requests follow-up to P3
Tests
- FollowEndpointTests rewritten for new envelopes: 11 → 12 cases incl.
pagination decode, mutual counts, requests envelope, query params
- Fixtures.swift: followUserObject updated (avatar, followId, status);
followersEnvelope/followingEnvelope helpers added; bare-array helper
removed
- SocialServiceTests: followers/following tests use the wrapped fixtures
Gate (run 2026-06-24)
- swift test InterlinedKit → 177/177 (174 → 177, +3)
- swift test InterlinedDomain → 244/244 (unchanged)
- swift test InterlinedPersistence → 70/70 (unchanged)
- xcodebuild test → 150/150 AppTests (unchanged)
Wave 1 deviation 5: CLOSED. Phase 6 (M5 Social) Wave 6.1 can now code
against the correct types without follow-up patches.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
+ Follow action backend ask 2.3b InterlinedDomain - Renamed module-marker enum InterlinedDomain → InterlinedDomain_Module to resolve the collision with the newly-added domain Notification type and Foundation.Notification. Public-API change confined to a low- traffic version marker; only consumer was InterlinedPersistence (also updated) InterlinedPersistence - Replaced the DomainNotificationAlias.swift workaround with the cleaner namespace rename above (file deleted) - NotificationRecordMapping.swift + SwiftDataNotificationStore.swift now use the disambiguated InterlinedDomain_Module marker / explicit InterlinedDomain.Notification form - New tests: SwiftDataNotificationStoreTests (11 cases — tray round-trip, mark-read flag flip, mark-all-read badge zero, every NotificationTarget case round-trip, clear cascade) and SwiftDataFollowCountsStoreTests (9 cases — follow + mutual combination, per-user isolation, second-write-wins, remove, clear) - Persistence count: 70 → 90 (+20) Domain models / services / tests (largely landed in da13846, 159f71a interleaved commits — closure covers the namespace + remaining tests): - Models: FollowRequest, FollowRelationship + FollowAction, MutualCounts, Notification, NotificationKind (8 typed cases + .other), NotificationTarget, NotificationMappers - SocialService write surface: follow / unfollow / approve / reject / removeFollower / mutual / requests - NotificationsService: tray / markRead / markAllRead (drops client- side limit since the server uses notificationTrayLimit clamped 10-40) - Domain count: 244 → 299 (+55) API-backend-prompts-to-build.md - Added P2 item 2.3b: POST /api/follow/[userId] response should carry the resulting relationship state so the macOS client can skip the follow-up GET /api/follow/[userId]/status round-trip Gate - swift test InterlinedKit → 177/177 unchanged - swift test InterlinedDomain → 299/299 passing (+55) - swift test InterlinedPersistence → 90/90 passing (+20) - xcodebuild test → 150/150 AppTests unchanged - grep "import AppKit" Packages/{Domain,Persistence} → empty - grep "import InterlinedKit" App/Features/Navigation/MenuCommands → empty - Decision 0003 holds Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
No description provided.