Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ jobs:
Application/DevLogInfra/DevLogInfra.xcodeproj \
Application/DevLogPersistence/DevLogPersistence.xcodeproj \
Application/DevLogPresentation/DevLogPresentation.xcodeproj \
Application/DevLogUI/DevLogUI.xcodeproj \
Widget/DevLogWidgetCore/DevLogWidgetCore.xcodeproj \
Widget/DevLogWidgetExtension/DevLogWidgetExtension.xcodeproj

Expand Down
5 changes: 3 additions & 2 deletions .hermes/skills/devlog-architecture-harness/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@ Ask the user before editing when:
- Core vs Domain ownership is unclear.
- A shared type is being moved only because multiple modules need access.
- Firebase/Auth/Firestore/Functions/Messaging-specific logic would leave Infra.
- WidgetCore would depend on Domain, Data, Infra, Persistence, Presentation, or App.
- Presentation would depend on Data, Infra, Persistence, or App.
- WidgetCore would depend on Domain, Data, Infra, Persistence, Presentation, UI, or App.
- Presentation would depend on UI, Data, Infra, Persistence, or App.
- UI would depend on Domain, Data, Infra, Persistence, or App.
- Data would gain concrete SDK or storage implementation details.
- The Presentation `Store` flow or reducer responsibility would change.
- A compile fix requires relaxing the intended architecture.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ flowchart TD
```mermaid
flowchart TD
App["DevLogApp\nComposition root\nApp lifecycle\nAssembler wiring"]
Presentation["DevLogPresentation\nSwiftUI views\nViewModels\nCoordinators\nUI state"]
UI["DevLogUI\nSwiftUI views\nReusable components\nView composition"]
Presentation["DevLogPresentation\nStore\nViewModels\nCoordinators\nUI state"]
Domain["DevLogDomain\nEntities\nRepository protocols\nUse cases"]
Data["DevLogData\nRepository implementations\nDTOs\nMappers\nService/store protocols"]
Infra["DevLogInfra\nFirebase\nSocial login\nNetwork\nLink metadata\nMessaging"]
Expand All @@ -83,13 +84,17 @@ flowchart TD
WidgetExtension["DevLogWidgetExtension\nWidgetKit UI\nProviders\nTimelines"]

App --> Presentation
App --> UI
App --> Domain
App --> Data
App --> Infra
App --> Persistence
App --> Core
App --> WidgetCore

UI --> Presentation
UI --> Core

Presentation --> Domain
Presentation --> Core

Expand All @@ -114,13 +119,14 @@ flowchart TD
| Layer | Owns | Allowed direction | Ask before |
| --- | --- | --- | --- |
| `DevLogCore` | DI primitives, logger, shared value/query types, widget snapshot values | No DevLog layer dependency | Moving domain entities into Core |
| `DevLogDomain` | entities, repository protocols, use cases | Core only | Adding Data, Infra, Persistence, Presentation, App, Widget UI, or SDK dependency |
| `DevLogDomain` | entities, repository protocols, use cases | Core only | Adding Data, Infra, Persistence, Presentation, UI, App, Widget UI, or SDK dependency |
| `DevLogData` | repository implementations, DTOs, mappers, data protocols | Domain, Core | Adding concrete Firebase, WidgetKit, storage, or platform implementation details |
| `DevLogInfra` | Firebase, social login, network, metadata, messaging implementations | Data, Core | Moving SDK-specific behavior out of Infra |
| `DevLogPersistence` | local stores, image cache, widget snapshot persistence | Data, Core, WidgetCore | Moving domain logic or remote SDK behavior into Persistence |
| `DevLogPresentation` | UI, view models, coordinators, presentation state | Domain, Core | Adding Data, Infra, Persistence, or App dependency |
| `DevLogPresentation` | Store, view models, coordinators, presentation state | Domain, Core | Adding UI, Data, Infra, Persistence, or App dependency |
| `DevLogUI` | SwiftUI views, reusable UI components, view composition | Presentation, Core | Adding Domain, Data, Infra, Persistence, or App dependency |
| `DevLogApp` | composition root, lifecycle, assembler wiring | Concrete app layers | Moving feature logic into App |
| `DevLogWidgetCore` | widget data contracts and pure snapshot logic | Core | Adding Domain, Data, Infra, Persistence, Presentation, or App dependency |
| `DevLogWidgetCore` | widget data contracts and pure snapshot logic | Core | Adding Domain, Data, Infra, Persistence, Presentation, UI, or App dependency |
| `DevLogWidgetExtension` | WidgetKit rendering and timeline plumbing | WidgetCore | Calling app/domain services directly |

## Presentation Store flow
Expand Down Expand Up @@ -159,6 +165,7 @@ flowchart TD
Shared{"Moved only because shared?"}
NewDependency{"New module dependency?"}
ExternalSDK{"External SDK crosses layer?"}
UIBoundary{"UI sees Domain/Data/Infra/Persistence/App?"}
WidgetBoundary{"WidgetCore sees app/domain/data?"}
BuildShortcut{"Build fix relaxes boundary?"}
ScopeDrift{"Outside current task scope?"}
Expand All @@ -173,7 +180,9 @@ flowchart TD
NewDependency -->|Yes| Ask
NewDependency -->|No| ExternalSDK
ExternalSDK -->|Yes| Ask
ExternalSDK -->|No| WidgetBoundary
ExternalSDK -->|No| UIBoundary
UIBoundary -->|Yes| Ask
UIBoundary -->|No| WidgetBoundary
WidgetBoundary -->|Yes| Ask
WidgetBoundary -->|No| BuildShortcut
BuildShortcut -->|Yes| Ask
Expand Down
8 changes: 5 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,14 @@ Treat this repository as an Xcode workspace-based modular iOS app. There is no r
### Current layer map

- `Application/DevLogCore`: shared app primitives such as DI, logging, query/value types, display options, and widget snapshot value types. Core is shared, but shared access alone is not enough reason to move domain entities into Core.
- `Application/DevLogDomain`: entities, repository protocols, use case protocols, and use case implementations. Domain may depend on Core. Domain must not depend on Data, Infra, Persistence, Presentation, App, Widget extension UI, Firebase SDKs, or storage implementations.
- `Application/DevLogDomain`: entities, repository protocols, use case protocols, and use case implementations. Domain may depend on Core. Domain must not depend on Data, Infra, Persistence, Presentation, UI, App, Widget extension UI, Firebase SDKs, or storage implementations.
- `Application/DevLogData`: repository implementations, DTOs, mappers, data-layer protocols for external services/stores, and widget sync coordination. Data may depend on Domain and Core. Data should not gain direct Firebase, GoogleSignIn, WidgetKit, or concrete storage implementation details unless the user explicitly approves the boundary change.
- `Application/DevLogInfra`: Firebase, social login, network, link metadata, messaging, and platform service implementations. Infra may depend on Data and Core. Firebase/Auth/Firestore/Functions/Messaging-specific behavior belongs here unless the user approves another boundary.
- `Application/DevLogPersistence`: local persistence, user defaults, image store, and widget snapshot persistence/updating. Persistence may depend on Data, Core, and WidgetCore when needed for snapshot persistence.
- `Application/DevLogPresentation`: SwiftUI views, view models, coordinators, UI state structures, and presentation-only helpers. Presentation may depend on Domain and Core. It must not depend on Data, Infra, Persistence, or App.
- `Application/DevLogPresentation`: Store, view models, coordinators, UI state structures, and presentation-only helpers. Presentation may depend on Domain and Core. It must not depend on UI, Data, Infra, Persistence, or App.
- `Application/DevLogUI`: SwiftUI views, reusable UI components, and view composition. UI may depend on Presentation and Core. It must not depend on Domain, Data, Infra, Persistence, or App.
- `Application/DevLogApp`: composition root, app lifecycle, app delegate, app-level routing, and assembler wiring. App may import concrete layers to assemble the dependency graph.
- `Widget/DevLogWidgetCore`: widget snapshot models, factories, keys, app-group constants, and widget-only pure helpers. WidgetCore may depend on Core. It must not depend on Domain, Data, Infra, Persistence, Presentation, or App without explicit user approval.
- `Widget/DevLogWidgetCore`: widget snapshot models, factories, keys, app-group constants, and widget-only pure helpers. WidgetCore may depend on Core. It must not depend on Domain, Data, Infra, Persistence, Presentation, UI, or App without explicit user approval.
- `Widget/DevLogWidgetExtension`: WidgetKit UI, widget providers, entries, timelines, and extension resources. It should consume WidgetCore outputs rather than app/domain services directly.
- `Firebase/functions`: TypeScript Cloud Functions. Deploy updated functions one by one separately.

Expand All @@ -68,6 +69,7 @@ Ask the user before editing when any of these are true:
- A type could plausibly live in both Core and Domain.
- A shared type is being moved only because multiple modules need access to it.
- A new target dependency would make a lower-level module know a higher-level module.
- UI would start depending on Domain, Data, Infra, Persistence, or App.
- A build fix would be achieved by loosening an architecture boundary.
- Firebase, GoogleSignIn, AuthenticationServices, UserNotifications, LinkPresentation, Network, WidgetKit, or storage implementation details would move to another layer.
- A repository protocol, service protocol, assembler, or DI ownership boundary would change.
Expand Down
22 changes: 22 additions & 0 deletions Application/DevLogApp/DevLogApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@

/* Begin PBXBuildFile section */
014266F6F7BFECD66B6DFFAE /* DevLogPersistence.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 2242DBABD8ABF11A23160669 /* DevLogPersistence.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
0839F65046F9E9A7CD41A531 /* DevLogUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B1D82B3A3D99364567064B1 /* DevLogUI.framework */; };
088CF111FEB9773D36BFDAFB /* LocalFirebaseRESTSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6750095BE316E855E5B6DFFB /* LocalFirebaseRESTSupport.swift */; };
0B79D44CFEC6A20F873CF3A9 /* DevLogCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B1F60100641A6FBC29ED402D /* DevLogCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
1056A18C4306853A83487239 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 53D7AC935B97855AA743EFC0 /* Assets.xcassets */; };
1FF94A7355AB32086A27E3F8 /* WidgetSessionSyncHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617DE060FF8750F5A41BBFCC /* WidgetSessionSyncHandler.swift */; };
23B23964D44D78E8CFF0D97C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC12B5E227050E4BAEF315D /* AppDelegate.swift */; };
25E825500273F1F887CCC6DC /* NotificationName+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EADB630F7E3DEFAA6A8DEF4 /* NotificationName+.swift */; };
2C2180289EEF65F3CF5C1273 /* DevLogWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D0044D9310C29492954FD1F7 /* DevLogWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
37996815654B34E69C60877B /* DevLogUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4B1D82B3A3D99364567064B1 /* DevLogUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
3BFF117D0E2682ECB0044884 /* MainTab+WidgetDeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAEEC97A4EC302C6974E1A4E /* MainTab+WidgetDeepLink.swift */; };
3CC295543F33CF6BE9560AE0 /* WidgetSessionSyncHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 462271192C47C593C36AC403 /* WidgetSessionSyncHandlerTests.swift */; };
423070F73AE0E059BC417D30 /* DevLogPresentation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ECA33E1403AF8BDB84D46C01 /* DevLogPresentation.framework */; };
Expand All @@ -35,7 +37,9 @@
AE8FAFB84865A9798429346B /* DevLogPersistence.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2242DBABD8ABF11A23160669 /* DevLogPersistence.framework */; };
B154A155136A179166B00EAC /* DevLogApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44AFCDB8890714D634C4536 /* DevLogApp.swift */; };
CED2148B1FA2117B58665B63 /* DevLogWidgetExtension.appex in Dependencies */ = {isa = PBXBuildFile; fileRef = D0044D9310C29492954FD1F7 /* DevLogWidgetExtension.appex */; };
CF9A2B14D691948BA64EAF7D /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8713EA9CC7AFF3F0A584863 /* RootView.swift */; };
D892FE40671E7C165DBE4E4D /* DevLogInfra.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 28ECC31B0B7F9C0A9BB4F55C /* DevLogInfra.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
DAA07C6782ECCD9D9047D56D /* TodoEditorWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4CC786C01FCD38075E60D0 /* TodoEditorWindowView.swift */; };
DF971A154668D99F2A646957 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 026FE38633FEFB0DD0575244 /* GoogleService-Info.plist */; };
E312AB98EB0B33141E499A04 /* DeleteWebPageIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B937350A0682E6F1CD20536 /* DeleteWebPageIntegrationTests.swift */; };
EDE9714BCCC50B8950F0E911 /* DevLogPresentation.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = ECA33E1403AF8BDB84D46C01 /* DevLogPresentation.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
Expand Down Expand Up @@ -77,6 +81,7 @@
D892FE40671E7C165DBE4E4D /* DevLogInfra.framework in Embed Frameworks */,
014266F6F7BFECD66B6DFFAE /* DevLogPersistence.framework in Embed Frameworks */,
EDE9714BCCC50B8950F0E911 /* DevLogPresentation.framework in Embed Frameworks */,
37996815654B34E69C60877B /* DevLogUI.framework in Embed Frameworks */,
F2F380C38E8953DB78F85FC2 /* DevLogWidgetCore.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
Expand Down Expand Up @@ -111,11 +116,13 @@
2242DBABD8ABF11A23160669 /* DevLogPersistence.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogPersistence.framework; sourceTree = BUILT_PRODUCTS_DIR; };
28ECC31B0B7F9C0A9BB4F55C /* DevLogInfra.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogInfra.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2B937350A0682E6F1CD20536 /* DeleteWebPageIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteWebPageIntegrationTests.swift; sourceTree = "<group>"; };
2C4CC786C01FCD38075E60D0 /* TodoEditorWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoEditorWindowView.swift; sourceTree = "<group>"; };
346173767E6ED68E9DFDA51C /* FCMTokenSyncHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCMTokenSyncHandler.swift; sourceTree = "<group>"; };
3494F50D6E7BC9510DE43F65 /* DevLogDomain.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogDomain.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3AC12B5E227050E4BAEF315D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
462271192C47C593C36AC403 /* WidgetSessionSyncHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetSessionSyncHandlerTests.swift; sourceTree = "<group>"; };
4635BA36DE82C73A105E7157 /* Version.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Version.xcconfig; sourceTree = "<group>"; };
4B1D82B3A3D99364567064B1 /* DevLogUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
534E5D11EAA2E34D71F066DB /* PushNotificationRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationRoute.swift; sourceTree = "<group>"; };
53D7AC935B97855AA743EFC0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
544C918C2E050DFD80C83157 /* DevLogData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogData.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand All @@ -135,6 +142,7 @@
C44AFCDB8890714D634C4536 /* DevLogApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevLogApp.swift; sourceTree = "<group>"; };
CAEEC97A4EC302C6974E1A4E /* MainTab+WidgetDeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainTab+WidgetDeepLink.swift"; sourceTree = "<group>"; };
D0044D9310C29492954FD1F7 /* DevLogWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = DevLogWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
D8713EA9CC7AFF3F0A584863 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = "<group>"; };
DCC05042BEB7C047BE4DB7E9 /* DevLogWidgetCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogWidgetCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
ECA33E1403AF8BDB84D46C01 /* DevLogPresentation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogPresentation.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F078E79DE65AE8D92F556B27 /* DeletePushNotificationIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletePushNotificationIntegrationTests.swift; sourceTree = "<group>"; };
Expand All @@ -151,6 +159,7 @@
AA5C5ACB75796DECF2BE6939 /* DevLogInfra.framework in Frameworks */,
AE8FAFB84865A9798429346B /* DevLogPersistence.framework in Frameworks */,
423070F73AE0E059BC417D30 /* DevLogPresentation.framework in Frameworks */,
0839F65046F9E9A7CD41A531 /* DevLogUI.framework in Frameworks */,
A93F228DD04515584B150BCE /* DevLogWidgetCore.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -195,6 +204,7 @@
28ECC31B0B7F9C0A9BB4F55C /* DevLogInfra.framework */,
2242DBABD8ABF11A23160669 /* DevLogPersistence.framework */,
ECA33E1403AF8BDB84D46C01 /* DevLogPresentation.framework */,
4B1D82B3A3D99364567064B1 /* DevLogUI.framework */,
DCC05042BEB7C047BE4DB7E9 /* DevLogWidgetCore.framework */,
D0044D9310C29492954FD1F7 /* DevLogWidgetExtension.appex */,
);
Expand Down Expand Up @@ -255,6 +265,7 @@
FC39B353F5CB051101B824C1 /* Handler */,
D4C5B7F48B3861AC6A9E4972 /* Notification */,
8B85D312D1D8054055A7F251 /* Routing */,
A3B5438D0D9EDCCCA37FEB88 /* Scene */,
C44AFCDB8890714D634C4536 /* DevLogApp.swift */,
B347482071F5035929CB7CFB /* TempView.swift */,
);
Expand Down Expand Up @@ -289,6 +300,15 @@
name = Project;
sourceTree = "<group>";
};
A3B5438D0D9EDCCCA37FEB88 /* Scene */ = {
isa = PBXGroup;
children = (
D8713EA9CC7AFF3F0A584863 /* RootView.swift */,
2C4CC786C01FCD38075E60D0 /* TodoEditorWindowView.swift */,
);
path = Scene;
sourceTree = "<group>";
};
A921B89C54E83E78FB2AC773 /* Derived */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -484,6 +504,8 @@
25E825500273F1F887CCC6DC /* NotificationName+.swift in Sources */,
3BFF117D0E2682ECB0044884 /* MainTab+WidgetDeepLink.swift in Sources */,
5B1CB12023BE41B32EA64B95 /* PushNotificationRoute.swift in Sources */,
CF9A2B14D691948BA64EAF7D /* RootView.swift in Sources */,
DAA07C6782ECCD9D9047D56D /* TodoEditorWindowView.swift in Sources */,
47A3EA049D3AF319B62853ED /* TempView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
1 change: 1 addition & 0 deletions Application/DevLogApp/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ let project = Project(
entitlements: .file(path: "Sources/Resource/DevLog.entitlements"),
dependencies: [
.project(target: "DevLogPresentation", path: "../DevLogPresentation"),
.project(target: "DevLogUI", path: "../DevLogUI"),
.project(target: "DevLogPersistence", path: "../DevLogPersistence"),
.project(target: "DevLogInfra", path: "../DevLogInfra"),
.project(target: "DevLogData", path: "../DevLogData"),
Expand Down
Loading