Skip to content
Merged
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
7 changes: 3 additions & 4 deletions .hermes/skills/devlog-architecture-harness/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Ask the user before editing when:
- WidgetCore would depend on Domain, Data, Infra, Persistence, Presentation, or App.
- Presentation would depend on Data, Infra, Persistence, or App.
- Data would gain concrete SDK or storage implementation details.
- The Presentation `Store` flow or reducer responsibility would change.
- The Presentation `StorePattern` flow or reducer responsibility would change.
- A compile fix requires relaxing the intended architecture.
- The change is outside the requested issue or PR scope.

Expand All @@ -88,16 +88,15 @@ When the boundary is clear:
- Keep the diff limited to the requested task.
- Preserve existing logic unless the user explicitly approved logic changes.
- Prefer existing DevLog naming and layer patterns.
- Preserve the existing Presentation `Store` pattern: `@MainActor`, `State`, `Action`, `SideEffect`, and `send -> reduce -> run`.
- Preserve the existing Presentation `StorePattern`: `@MainActor`, `State`, `Action`, `SideEffect`, and `send -> reduce -> run`.
- Do not introduce unrelated cleanup.
- Do not change lockfiles unless dependency resolution is part of the task.

### 5. Verify

For Swift/iOS code changes:

- Use Xcode Local MCP for the build.
- If Xcode Local MCP is unavailable, say so and ask before using another path unless the user already approved a fallback.
- Follow the repository `AGENTS.md` Verification section.
- Inspect the final diff for architecture-scope drift.

For docs-only or harness-only changes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,12 @@ flowchart TD
| `DevLogWidgetCore` | widget data contracts and pure snapshot logic | Core | Adding Domain, Data, Infra, Persistence, Presentation, or App dependency |
| `DevLogWidgetExtension` | WidgetKit rendering and timeline plumbing | WidgetCore | Calling app/domain services directly |

## Presentation Store flow
## Presentation StorePattern flow

```mermaid
flowchart LR
View["SwiftUI View"]
ViewModel["ViewModel / Store"]
ViewModel["ViewModel / StorePattern"]
Send["send(Action)"]
Reduce["reduce(with:)"]
State["State update"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ This reference holds DevLog-specific working rules that should live with the pro

## Verification

- Follow `AGENTS.md` for the canonical lint and build verification policy.
- Run Homebrew SwiftLint (`swiftlint`) on changed Swift files.
- Lint production Swift files with the applicable source `.swiftlint.yml` config.
- Lint test Swift files with `.swiftlint-tests.yml` or the module `Tests/.swiftlint.yml` that inherits from it. Do not use the root production config for tests.
- Prefer Xcode Local MCP for iOS project code changes.
- If Xcode Local MCP is unavailable or fails because of session transport, state that explicitly before using a fallback.
- This repository is workspace-based. Prefer workspace/scheme context over standalone project builds when dependencies cross module projects.
Expand Down Expand Up @@ -61,9 +65,9 @@ This reference holds DevLog-specific working rules that should live with the pro
- For example, keep the app-facing Domain query separate from an Infra-facing Data query when that avoids Domain coupling in service protocols.
- Firebase-specific error detection belongs in Infra; Data should handle domain-level errors after mapping.

## Presentation Store
## Presentation StorePattern

- Preserve the existing `Store` shape: `@MainActor`, `State`, `Action`, `SideEffect`, `send -> reduce -> run`.
- Preserve the existing `StorePattern` shape: `@MainActor`, `State`, `Action`, `SideEffect`, `send -> reduce -> run`.
- Reducers compute state and return side effects.
- I/O belongs in `run` or injected services.
- Do not leave reducer-era helper methods behind after moving work into `run`.
Expand Down
21 changes: 8 additions & 13 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ These instructions apply only to the repository root.

## Naming and Swift style

- Set variables named after a type using the type's full name in camel case.
- In Swift, do not write explicit type annotations unless required.
- Use `opfic` in new Swift file headers.
- Prefer `<` and `<=` over `>` and `>=` when writing comparisons, if the condition can be expressed clearly that way.
Expand All @@ -37,7 +36,7 @@ Treat this repository as a Tuist-generated, workspace-based modular iOS app. The
4. Classify the change as mechanical, architectural, or ambiguous.
5. For ambiguous architecture changes, stop and ask the user before editing.
6. Keep the diff limited to the requested architecture scope.
7. After Swift/iOS code changes, verify with Xcode Local MCP.
7. After Swift/iOS code changes, follow the Verification section.
8. Report the changed files, architecture decision, and verification result.

### Current layer map
Expand All @@ -53,10 +52,10 @@ Treat this repository as a Tuist-generated, workspace-based modular iOS app. The
- `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.

### Store flow
### StorePattern flow

- Preserve the existing Presentation `Store` pattern.
- `Store` is `@MainActor` and uses `State`, `Action`, `SideEffect`, and the `send -> reduce -> run` flow.
- Preserve the existing Presentation `StorePattern`.
- `StorePattern` is `@MainActor` and uses `State`, `Action`, `SideEffect`, and the `send -> reduce -> run` flow.
- Reducers should compute state and return side effects.
- I/O belongs in `run` or injected services, not in reducer state computation.
- Ask before changing reducer, side-effect, or ViewModel responsibility boundaries.
Expand Down Expand Up @@ -86,7 +85,10 @@ These may proceed after inspection when they do not change architecture meaning:

## Verification

- If iOS project code changes, test build with Xcode Local MCP.
- If Swift files change, run Homebrew SwiftLint (`swiftlint`) on the changed Swift files.
- For production Swift files, use the applicable source `.swiftlint.yml` config.
- For test Swift files, use `.swiftlint-tests.yml` or the module `Tests/.swiftlint.yml` that inherits from it. Do not lint tests with the root production config.
- If iOS project code changes, verify with Xcode Local MCP when it is available.
- If Xcode Local MCP is unavailable, state that explicitly before using a fallback.
- Do not claim architecture work is complete without checking the diff scope.
- Do not spend time on unrelated generated project or lockfile churn. Keep generated workspace/project and `Package.resolved` changes out of source control unless they are part of an explicitly approved dependency-lock policy.
Expand All @@ -98,10 +100,3 @@ These may proceed after inspection when they do not change architecture meaning:
- Treat `AGENTS.md` and `.hermes/skills/devlog-architecture-harness` as the canonical DevLog AI working rules.
- If global memory conflicts with this repository, follow this repository.
- For PR, commit, Xcode project, CI, widget, Store, localization, or release workflow details, read `.hermes/skills/devlog-architecture-harness/references/devlog-workflow-rules.md`.

## Sub-agent use

- Consider sub-agent use on every non-trivial task.
- Use sub-agents only for independent research, verification, or disjoint implementation work.
- Do not use sub-agents for sequential steps, overlapping file edits, or tightly coupled refactors.
- The main agent remains responsible for planning, integration, final verification, and user communication.
1 change: 1 addition & 0 deletions Application/DevLogApp/Sources/App/DevLogApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ struct DevLogApp: App {
networkConnectivityUseCase: container.resolve(ObserveNetworkConnectivityUseCase.self),
systemThemeUseCase: container.resolve(ObserveSystemThemeUseCase.self),
trackAnalyticsEventUseCase: container.resolve(TrackAnalyticsEventUseCase.self),
signInUseCase: container.resolve(SignInUseCase.self),
widgetURLTab: { MainTab(widgetURL: $0) },
windowEvent: windowEvent,
pushNotificationTodoIdPublisher: PushNotificationRoute.shared.observe(),
Expand Down
2 changes: 1 addition & 1 deletion Application/DevLogCore/Sources/ActivityKind.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

public enum ActivityKind: String, Hashable {
public enum ActivityKind: String, Hashable, Sendable {
case created
case completed
case deleted
Expand Down
2 changes: 1 addition & 1 deletion Application/DevLogCore/Sources/Logger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation
import os.log

public final class Logger {
public final class Logger: Sendable {
private let subsystem: String
private let category: String
private let osLog: OSLog
Expand Down
6 changes: 3 additions & 3 deletions Application/DevLogCore/Sources/TodayDisplayOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@

import Foundation

public struct TodayDisplayOptions: Equatable {
public enum DueDateVisibility: String, CaseIterable, Equatable {
public struct TodayDisplayOptions: Equatable, Sendable {
public enum DueDateVisibility: String, CaseIterable, Equatable, Sendable {
case all
case withDueDateOnly
case withoutDueDateOnly
}

public enum FocusVisibility: String, CaseIterable, Equatable {
public enum FocusVisibility: String, CaseIterable, Equatable, Sendable {
case all
case focusedOnly
}
Expand Down
2 changes: 1 addition & 1 deletion Application/DevLogData/Sources/Protocol/AuthService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Combine
import Foundation

public protocol AuthService {
public protocol AuthService: Sendable {
var uid: String? { get }
var providerIDs: [String] { get }
var currentUserEmail: String? { get }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

public protocol AuthenticationService {
public protocol AuthenticationService: Sendable {
func signIn() async throws -> AuthDataResponse
func signOut(_ uid: String) async throws
func deleteAuth(_ uid: String) async throws
Expand Down
2 changes: 1 addition & 1 deletion Application/DevLogData/Sources/Protocol/UserService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

public protocol UserService {
public protocol UserService: Sendable {
func upsertUser(_ response: AuthDataResponse) async throws
func fetchUserProfile() async throws -> UserProfileResponse
func upsertStatusMessage(_ message: String) async throws
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation
import DevLogCore

public protocol WidgetSnapshotPreferenceStore {
public protocol WidgetSnapshotPreferenceStore: Sendable {
func heatmapActivityTypes() -> [String]
func setHeatmapActivityTypes(_ activityTypes: [String])
func selectedActivityKinds() -> Set<ActivityKind>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation
import DevLogCore

public protocol WidgetSnapshotUpdater {
public protocol WidgetSnapshotUpdater: Sendable {
func updateTodaySnapshot(
todos: [WidgetTodoSnapshot],
now: Date
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

public protocol AuthenticationRepository {
public protocol AuthenticationRepository: Sendable {
func signIn(_ provider: AuthProvider) async throws
func signOut() async throws
func restore() -> Bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
// Created by 최윤진 on 11/2/25.
//

public protocol SignInUseCase {
public protocol SignInUseCase: Sendable {
func execute(_ provider: AuthProvider) async throws
}
47 changes: 47 additions & 0 deletions Application/DevLogInfra/Sources/Common/FirebaseDependency.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// FirebaseDependency.swift
// DevLogInfra
//
// Created by opfic on 6/5/26.
//

import FirebaseAuth
import FirebaseFirestore
import FirebaseFunctions
import FirebaseMessaging

struct FirebaseDependency<Value>: @unchecked Sendable {
private let value: Value

init(value: Value) {
self.value = value
}
}

extension FirebaseDependency where Value == Firestore {
func document(_ path: String) -> DocumentReference {
value.document(path)
}
}

extension FirebaseDependency where Value == Functions {
func httpsCallable(_ name: some RawRepresentable<String>) -> HTTPSCallable {
value.httpsCallable(name)
}
}

extension FirebaseDependency where Value == Messaging {
func token() async throws -> String {
try await value.token()
}

func deleteToken() async throws {
try await value.deleteToken()
}
}

extension FirebaseDependency where Value == AuthStateDidChangeListenerHandle {
func removeAuthStateDidChangeListener() {
Auth.auth().removeStateDidChangeListener(value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import UIKit
import DevLogData

final class TopViewControllerProvider {
final class TopViewControllerProvider: Sendable {
@MainActor
func topViewController() -> UIViewController? {
guard let keyWindow = keyWindow() else {
Expand Down
Loading