@@ -94,6 +94,15 @@ final class AppEnvironment: ObservableObject {
9494 /// doubles substitute in.
9595 let documentsService : DocumentsServicing
9696
97+ /// The server-side document-templates surface (the-gaps.md G12) — the
98+ /// user's own saved template documents. Distinct from the client-side
99+ /// `DocumentTemplate.builtIn` catalog: this lists / creates-from / seeds
100+ /// server templates so the "New from Template" picker can show a
101+ /// "Your templates" section. Exposed as the protocol so test doubles
102+ /// substitute in. Ungated, so it is a plain stored service (no live
103+ /// entitlements rebuild).
104+ let documentTemplatesService : DocumentTemplatesServicing
105+
97106 /// The owner of `/api/documents/sync` — the M4 offline backbone
98107 /// (PLAN.md §3, §6 M4). The App layer reaches in directly for the
99108 /// `syncNow()` button on the toolbar; the rest of the App talks to
@@ -150,6 +159,71 @@ final class AppEnvironment: ObservableObject {
150159 /// endpoints and returns domain `CSVExport` values.
151160 let exportsService : ExportsServicing
152161
162+ /// The global-search surface the Search feature binds against
163+ /// (the-gaps.md G5). Exposed as the protocol so test doubles
164+ /// substitute in. Fans out over messages / lists / documents and
165+ /// returns domain values.
166+ let search : SearchServicing
167+
168+ /// The moderation surface the Blocked & Muted settings pane and the
169+ /// per-user / per-message report affordances bind against
170+ /// (the-gaps.md G2). Exposed as the protocol so test doubles
171+ /// substitute in.
172+ let moderation : ModerationServicing
173+
174+ /// The Direct Messages surface the Messages feature binds against
175+ /// (the-gaps.md G1). Exposed as the protocol so test doubles
176+ /// substitute in. Wraps the `/api/messages/*` DM endpoints (folders,
177+ /// thread, send, recipients, unread, read/trash/restore) and returns
178+ /// domain `DirectMessage` / `DMThread` / `DMPage` values.
179+ let directMessages : DirectMessagesServicing
180+
181+ /// Cross-window event bus for the Direct Messages feature (the-gaps.md
182+ /// G1). Sending / reading / trashing posts to this bus so the DM list,
183+ /// an open thread, and the unread-badge coordinator update in place
184+ /// without a refetch.
185+ let directMessagesEventBus : DirectMessagesEventBus
186+
187+ /// Live list-folders surface (the-gaps.md G6). Folders are
188+ /// subscriber-gated on create, so the service is rebuilt on each
189+ /// access with the current account's entitlements — mirroring
190+ /// `liveEntitlements`, so a mid-session subscription change re-gates
191+ /// folder creation without stale state. The read / rename / move /
192+ /// delete paths are ungated and unaffected.
193+ var listFolders : ListFoldersServicing {
194+ ListFoldersService (
195+ api: listFoldersAPI,
196+ entitlements: EntitlementsService ( user: currentUserStore. currentUser)
197+ )
198+ }
199+
200+ /// The shared kit-layer API client retained so `listFolders` can
201+ /// rebuild the folders service with live entitlements on each access.
202+ private let listFoldersAPI : APIClientProtocol
203+
204+ /// Live share-links surface (the-gaps.md G3). Creating a link is
205+ /// subscriber-gated, so — exactly like `listFolders` — the service is
206+ /// rebuilt on each access with the current account's entitlements, so a
207+ /// mid-session subscription change re-gates link creation without stale
208+ /// state. The list / resolve / revoke / claim paths are ungated and
209+ /// unaffected.
210+ var sharing : SharingServicing {
211+ SharingService (
212+ api: sharingAPI,
213+ entitlements: EntitlementsService ( user: currentUserStore. currentUser)
214+ )
215+ }
216+
217+ /// The shared kit-layer API client retained so `sharing` can rebuild the
218+ /// sharing service with live entitlements on each access.
219+ private let sharingAPI : APIClientProtocol
220+
221+ /// Base URL used to compose canonical web share URLs (`…/lists/shared/…`)
222+ /// when the server does not return a pre-built `ShareLink.url`. Defaults
223+ /// to the production host; the App layer reads it through the domain-only
224+ /// `URL` type so no kit import leaks into the sharing views.
225+ let shareBaseURL : URL
226+
153227 /// Designated initializer used by tests and previews that want to
154228 /// inject a fully synthetic service graph. Production code calls
155229 /// `live()` instead.
@@ -163,6 +237,7 @@ final class AppEnvironment: ObservableObject {
163237 listsEventBus: ListsEventBus ,
164238 listsStore: ListsStore ,
165239 documentsService: DocumentsServicing ,
240+ documentTemplatesService: DocumentTemplatesServicing ,
166241 documentSyncEngine: DocumentSyncEngine ,
167242 documentSyncEvents: AsyncStream < DocumentSyncEvent > ,
168243 notificationsService: NotificationsServicing ,
@@ -171,7 +246,14 @@ final class AppEnvironment: ObservableObject {
171246 followRelationshipReader: FollowRelationshipReading ,
172247 orgService: OrgServicing ,
173248 userService: UserServicing ,
174- exportsService: ExportsServicing
249+ exportsService: ExportsServicing ,
250+ search: SearchServicing ,
251+ moderation: ModerationServicing ,
252+ directMessages: DirectMessagesServicing ,
253+ directMessagesEventBus: DirectMessagesEventBus ,
254+ listFoldersAPI: APIClientProtocol ,
255+ sharingAPI: APIClientProtocol ,
256+ shareBaseURL: URL
175257 ) {
176258 self . messages = messages
177259 self . lists = lists
@@ -182,6 +264,7 @@ final class AppEnvironment: ObservableObject {
182264 self . listsEventBus = listsEventBus
183265 self . listsStore = listsStore
184266 self . documentsService = documentsService
267+ self . documentTemplatesService = documentTemplatesService
185268 self . documentSyncEngine = documentSyncEngine
186269 self . documentSyncEvents = documentSyncEvents
187270 self . notificationsService = notificationsService
@@ -191,6 +274,13 @@ final class AppEnvironment: ObservableObject {
191274 self . orgService = orgService
192275 self . userService = userService
193276 self . exportsService = exportsService
277+ self . search = search
278+ self . moderation = moderation
279+ self . directMessages = directMessages
280+ self . directMessagesEventBus = directMessagesEventBus
281+ self . listFoldersAPI = listFoldersAPI
282+ self . sharingAPI = sharingAPI
283+ self . shareBaseURL = shareBaseURL
194284 }
195285
196286 /// Builds the production service graph:
@@ -284,6 +374,11 @@ final class AppEnvironment: ObservableObject {
284374 api: api,
285375 sync: documentSyncEngine
286376 )
377+ // Server document templates (the-gaps.md G12). Reuses the same
378+ // kit-layer `APIClient` like the other services do — the
379+ // `/api/documents/templates` endpoints are already routed by the
380+ // shared `authTransport`. Ungated, so a plain stored service.
381+ let documentTemplatesService = DocumentTemplatesService ( api: api)
287382 let documentSyncEvents = documentSyncEngine. events
288383 // M5 — Notifications + Social write surface (PLAN.md §6 M5).
289384 // `NotificationsService` already exists with the read + mark
@@ -307,6 +402,24 @@ final class AppEnvironment: ObservableObject {
307402 // decision-0001 session-only allowlist (`/api/exports/*`), already
308403 // routed by the shared `authTransport`.
309404 let exportsService = ExportsService ( api: api)
405+ // Web-parity batch (the-gaps.md G5 / G2 / G6). All three reuse the
406+ // same kit-layer `APIClient` like `lists` / `social` do — their
407+ // endpoints are already routed by the shared `authTransport`.
408+ // • Search — full-text over messages / lists / documents (G5).
409+ // • Moderation — blocks / mutes / reports (G2).
410+ // • List folders — the API client is retained on the environment
411+ // so `listFolders` can rebuild the service with live
412+ // entitlements per access (folders are subscriber-gated on
413+ // create, G6).
414+ let search = SearchService ( api: api)
415+ let moderation = ModerationService ( api: api)
416+ // Direct Messages (the-gaps.md G1). Reuses the same kit-layer
417+ // `APIClient` like `lists` / `social` / `search` do — the DM
418+ // endpoints are already routed by the shared `authTransport`. The
419+ // event bus is a singleton so the DM list, an open thread, and the
420+ // dock-badge coordinator all see the same stream.
421+ let directMessages = DirectMessagesService ( api: api)
422+ let directMessagesEventBus = DirectMessagesEventBus ( )
310423 return AppEnvironment (
311424 messages: messages,
312425 lists: lists,
@@ -317,6 +430,7 @@ final class AppEnvironment: ObservableObject {
317430 listsEventBus: listsEventBus,
318431 listsStore: listsStore,
319432 documentsService: documentsService,
433+ documentTemplatesService: documentTemplatesService,
320434 documentSyncEngine: documentSyncEngine,
321435 documentSyncEvents: documentSyncEvents,
322436 notificationsService: notificationsService,
@@ -325,7 +439,20 @@ final class AppEnvironment: ObservableObject {
325439 followRelationshipReader: followRelationshipReader,
326440 orgService: orgService,
327441 userService: userService,
328- exportsService: exportsService
442+ exportsService: exportsService,
443+ search: search,
444+ moderation: moderation,
445+ directMessages: directMessages,
446+ directMessagesEventBus: directMessagesEventBus,
447+ listFoldersAPI: api,
448+ // Share Links (the-gaps.md G3) reuse the same kit-layer
449+ // `APIClient`; the API client is retained on the environment so
450+ // `sharing` can rebuild the service with live entitlements per
451+ // access (link creation is subscriber-gated). The base URL feeds
452+ // the canonical web-URL builder for links the server returns
453+ // without a pre-built `url`.
454+ sharingAPI: api,
455+ shareBaseURL: InterlinedKit . defaultBaseURL
329456 )
330457 }
331458
0 commit comments