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
25 changes: 20 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,26 @@ jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v6
with:
fetch-depth: 2
- uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm
- run: corepack enable && corepack install
- run: pnpm install --frozen-lockfile
- run: pnpm check
- uses: astral-sh/setup-uv@v7
with:
enable-cache: false
- run: pnpm quality

native-quality:
runs-on: macos-15
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
- run: corepack enable && corepack install
- run: command -v xcodegen >/dev/null || brew install xcodegen
- run: pnpm quality:native
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ migrations, or credential changes.
- **Stack:** Vite + React + TypeScript, Cloudflare Worker, D1, Better Auth with Google
- **Package manager:** pnpm
- **Local dev:** `pnpm dev`
- **Checks:** `pnpm check`
- **Checks:** `pnpm quality` (alias: `pnpm check`); `pnpm quality:native` on macOS
- **Deploy:** `pnpm deploy` (manual only)

## Product rules
Expand Down
9 changes: 9 additions & 0 deletions PROJECT_STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ full sets/reps workout-programming system.

## Timeline

- 2026-08-12 — adopted the Fleet code-health contract as the single CI gate:
whole-source web and native coverage, unused code, cross-language complexity,
exact duplication, import/target cycles, severe advisories, suppression
markers, web/native builds, and repository hygiene now have explicit
non-regression floors or ceilings; removed unused exports and duplicate
aliases, and targeted tool updates reduced high advisories from two to zero
- 2026-08-11 — released native Sign in with Apple, explicit linking to an
existing Google-owned journal, deterministic iPhone/cloud reconciliation,
durable offline sync intents, and single-use Google-to-native handoffs; D1
Expand Down Expand Up @@ -100,6 +106,9 @@ full sets/reps workout-programming system.

## Features (shipped)

- CI-enforced mixed web/native Fleet code-health ratchets across whole-source
coverage, dead code, complexity, duplication, cycles, dependency advisories,
suppression markers, builds, and repository hygiene
- Native iPhone and iPad journal with local onboarding, food/water/weight/routine
capture, transparent calculations and guidance, progress/history, food
management, data transfer, appearance and accessibility support, simulator
Expand Down
4 changes: 4 additions & 0 deletions ios/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ A native SwiftUI nutrition journal for iOS 17 and later. It uses Apple framework
./scripts/check.sh
```

From the repository root, `pnpm quality:native` selects an available iPhone
simulator, runs the same tests and Release build, and enforces the checked-in
production Swift coverage floor. CI runs this command on macOS.

## Personal-team archive

```bash
Expand Down
21 changes: 15 additions & 6 deletions ios/Sources/CalorieCore/CloudJournal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ public enum JournalReconciliationChoice: Equatable, Sendable {
}

public enum CloudJournalMapper {
public static func decode(_ data: Data) throws -> CloudJournalSnapshot {
public static func decode(
_ data: Data,
calendar: Calendar = .current
) throws -> CloudJournalSnapshot {
let export = try JSONDecoder().decode(CloudExport.self, from: data)
guard export.schema == "calorie-journal-backup", export.version == 2 else {
throw CalorieError.unsupportedSchema(export.version)
Expand All @@ -52,7 +55,9 @@ public enum CloudJournalMapper {
let document = CalorieDocument(
profile: profile,
foods: foods,
foodEntries: export.entries.map { mapEntry($0, food: $0.foodId.flatMap { foodByID[$0] }) },
foodEntries: export.entries.map {
mapEntry($0, food: $0.foodId.flatMap { foodByID[$0] }, calendar: calendar)
},
waterEntries: export.waterEntries.map {
WaterEntry(id: stableUUID($0.id), timestamp: date($0.drankAt), millilitres: $0.amountMl)
},
Expand Down Expand Up @@ -201,7 +206,11 @@ public enum CloudJournalMapper {
)
}

private static func mapEntry(_ entry: CloudEntry, food: CloudFood?) -> FoodEntry {
private static func mapEntry(
_ entry: CloudEntry,
food: CloudFood?,
calendar: Calendar
) -> FoodEntry {
let servings: Double
if let food, food.servingMode == "per_100g", food.defaultAmount > 0 {
servings = entry.amount / food.defaultAmount
Expand All @@ -213,7 +222,7 @@ public enum CloudJournalMapper {
id: stableUUID(entry.id),
foodID: stableUUID(entry.foodId ?? "direct:\(entry.id)"),
foodName: entry.foodName,
meal: meal(for: timestamp),
meal: meal(for: timestamp, calendar: calendar),
timestamp: timestamp,
servings: max(0.01, servings),
nutrients: Nutrients(
Expand All @@ -226,8 +235,8 @@ public enum CloudJournalMapper {
)
}

private static func meal(for date: Date) -> Meal {
switch Calendar.current.component(.hour, from: date) {
private static func meal(for date: Date, calendar: Calendar) -> Meal {
switch calendar.component(.hour, from: date) {
case 0..<11: .breakfast
case 11..<16: .lunch
case 16..<21: .dinner
Expand Down
9 changes: 7 additions & 2 deletions ios/Tests/CalorieCoreTests/CalorieCoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,17 @@ final class CalorieCoreTests: XCTestCase {
}

func testCloudExportMapsCurrentJournalWithoutInventingFat() throws {
let snapshot = try CloudJournalMapper.decode(Data(Self.cloudExport.utf8))
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = try XCTUnwrap(TimeZone(secondsFromGMT: 0))
let snapshot = try CloudJournalMapper.decode(
Data(Self.cloudExport.utf8),
calendar: calendar
)

XCTAssertEqual(snapshot.document.profile.name, "Sarthak")
XCTAssertEqual(snapshot.counts.foodEntries, 1)
XCTAssertEqual(snapshot.document.foodEntries.first?.foodName, "Greek yoghurt")
XCTAssertEqual(snapshot.document.foodEntries.first?.meal, .breakfast)
XCTAssertEqual(snapshot.document.foodEntries.first?.meal, .snack)
XCTAssertEqual(snapshot.document.foodEntries.first?.nutrients.fat, 0)
XCTAssertEqual(snapshot.document.weightEntries.first?.kilograms, 72.4)
XCTAssertEqual(snapshot.document.profile.weightKilograms, 72.4)
Expand Down
9 changes: 9 additions & 0 deletions knip.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "https://unpkg.com/knip@6/schema.json",
"entry": ["public/sw.js"],
"project": ["src/**/*.{ts,tsx,mjs}", "*.config.ts"],
"ignoreDependencies": ["jscpd", "lightningcss"],
"ignoreIssues": {
"src/agent-edge.mjs": ["exports"]
}
}
24 changes: 20 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,21 @@
"typecheck": "tsc --noEmit -p tsconfig.app.json && tsc --noEmit -p tsconfig.worker.json",
"lint": "biome check .",
"format": "biome format --write .",
"format:check": "biome format .",
"test": "vitest run",
"check": "pnpm lint && pnpm typecheck && pnpm test && pnpm build"
"test:coverage": "vitest run --coverage",
"knip": "knip --no-exit-code --reporter symbols",
"knip:strict": "knip --reporter symbols",
"quality:unused": "node scripts/check-code-health.mjs unused",
"quality:complexity": "node scripts/check-code-health.mjs complexity",
"quality:duplication": "node scripts/check-code-health.mjs duplication",
"quality:cycles": "node scripts/check-code-health.mjs cycles",
"quality:dependencies": "node scripts/check-code-health.mjs dependencies",
"quality:suppressions": "node scripts/check-code-health.mjs suppressions",
"quality:hygiene": "node scripts/check-code-health.mjs hygiene",
"quality:native": "node scripts/check-native-code-health.mjs",
"quality": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm test:coverage && pnpm quality:unused && pnpm quality:complexity && pnpm quality:duplication && pnpm quality:cycles && pnpm quality:dependencies && pnpm quality:suppressions && pnpm build && pnpm quality:hygiene",
"check": "pnpm quality"
},
"dependencies": {
"better-auth": "1.6.25",
Expand All @@ -34,17 +47,20 @@
},
"devDependencies": {
"@biomejs/biome": "2.5.5",
"@cloudflare/workers-types": "5.20260727.1",
"@cloudflare/workers-types": "5.20260812.1",
"@types/react": "19.2.17",
"@types/react-dom": "19.2.3",
"@vitejs/plugin-react-swc": "4.3.2",
"@vitest/coverage-v8": "4.1.10",
"concurrently": "9.2.1",
"jscpd": "5.0.14",
"knip": "6.29.0",
"lightningcss": "1.33.0",
"typescript": "6.0.3",
"ultracite": "7.10.2",
"vite": "8.1.5",
"vite": "8.2.1",
"vitest": "4.1.10",
"wrangler": "4.114.0"
"wrangler": "4.121.0"
},
"pnpm": {
"overrides": {
Expand Down
Loading
Loading