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
79 changes: 12 additions & 67 deletions .cursor/rules/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,11 @@ When adding new table or list views with column headers, use the **Header 2** st

The app targets desktop (macOS) and tablet-class screens with ample real estate. Content must feel **comfortable and easy to scan**, not cramped. Follow these principles:

- **Minimum readable font is `.subheadline`** — never use `.caption2` for user-facing content. `.caption` is the absolute floor for metadata/timestamps. Use `.body` or larger for primary content.
- **Minimum readable font is `.subheadline`** — never use `.caption2` for user-facing **text content**. `.caption` is the absolute floor for metadata/timestamps. Use `.body` or larger for primary content.
- **`.caption2` is reserved for two non-text cases only, and nowhere else:**
1. **Inline SF Symbol glyphs** sized inside a chip/badge/indicator — e.g. the score-trend arrow, a filter-chip's leading icon, a disclosure chevron. Here `.caption2` sizes an *icon*, not body text.
2. **Spatial labels drawn on a `Canvas`** — e.g. network-graph node names, where a larger font overlaps adjacent nodes.
- All other labels (pill text, last-interaction dates, counts, badge/capsule labels, footnotes, disclaimers) use `.caption` or larger. Audited and enforced 2026-06-02.
- **Section labels** (field headings like "Phone", "Met via") use `.subheadline.weight(.semibold)` — not `.caption`.
- **Primary content values** (phone numbers, names, note bodies) use `.body` or larger.
- **Titles on detail screens** use `.title.weight(.bold)` — not `.title2` or smaller.
Expand All @@ -221,24 +225,13 @@ When in doubt, round **up** to the next font size / spacing tier. Small cramped

### Icon Styles

#### Icon 1
#### Icon 1 (`icon1Size`)

Used for prominent, standalone icon badges in collection rows and detail headers where the icon is a key visual anchor (e.g. Location rows and headers).
`AppConstants.UI.icon1Size` is **36** — the single canonical collection-badge size, identical to the Row Icon Badges below. There is intentionally **one** badge size across Tags, Groups, and Locations.

```swift
Image(systemName: icon)
.font(.title3)
.foregroundStyle(.white)
.frame(width: AppConstants.UI.icon1Size, height: AppConstants.UI.icon1Size)
.background(color.gradient, in: RoundedRectangle(cornerRadius: 10))
```

- **Size:** `AppConstants.UI.icon1Size` (48×48)
- **Corner radius:** 10
- **Icon font:** `.title3`
- **Background:** `color.gradient` inside `RoundedRectangle`
> **History:** earlier drafts defined `icon1Size` as a larger **48×48** badge (cornerRadius 10, `.title3`) to give Locations extra prominence. The app standardized on 36×36 for all collections; `icon1Size` is now 36 and no view renders a 48pt badge. Do not reintroduce a 48×48 variant. (See `CLAUDE.md`: "Do not use `icon1Size` for larger icons.")

#### Row Icon Badges (Tag, Group, and any future collection-type rows)
#### Row Icon Badges (Tag, Group, Location, and any future collection-type rows)

```swift
Image(systemName: icon)
Expand Down Expand Up @@ -279,30 +272,9 @@ HStack(spacing: 12) {

### Location Row Layout

Location rows use **Icon 1** for the badge and **Header 1** for the name to give locations greater visual prominence.
**Locations use the standard Row Layout above** (36×36 badge, `.body.weight(.medium)` name) — identical to Tags and Groups. `LocationRowView` matches `TagRowView`/`GroupRowView` exactly. (An earlier draft gave Locations a larger Icon-1 / Header-1 row; that prominence was removed when the app standardized on a single 36×36 collection row.)

```swift
HStack(spacing: 12) {
// Icon 1 badge (48×48, see Icon 1)
VStack(alignment: .leading, spacing: 4) {
Text(name)
.font(.title.weight(.bold))
Text("\(count) contact\(count == 1 ? "" : "s")")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
}
.padding(.vertical, 4)
```

- **Icon style:** Icon 1 (48×48)
- **Name font:** Header 1 — `.title.weight(.bold)`
- **Subtitle font:** `.caption`, `.secondary` foreground
- **VStack spacing:** 4
- **Vertical padding:** 4

### Detail View Headers (Tag, Group, and future collection detail pages)
### Detail View Headers (Tag, Group, Location, and future collection detail pages)

Use the same sizing as row icons — detail headers should feel like a natural extension of the list row, not a different component.

Expand Down Expand Up @@ -337,34 +309,7 @@ Section {

### Location Detail Header

Location detail headers use **Icon 1** and **Header 1** to match the Location Row Layout.

```swift
Section {
HStack(spacing: 12) {
Image(systemName: icon)
.font(.title3)
.foregroundStyle(.white)
.frame(width: AppConstants.UI.icon1Size, height: AppConstants.UI.icon1Size)
.background(color.color.gradient, in: RoundedRectangle(cornerRadius: 10))
VStack(alignment: .leading, spacing: 4) {
Text(name)
.font(.title.weight(.bold))
Text("\(count) contact\(count == 1 ? "" : "s")")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
}
.padding(.vertical, 4)
}
```

- **Icon style:** Icon 1 (48×48, cornerRadius 10, `.title3` font)
- **Name font:** Header 1 — `.title.weight(.bold)`
- **Subtitle font:** `.caption`
- **VStack spacing:** 4
- **Vertical padding:** 4
**Locations use the standard Detail View Header above** (36×36 badge, cornerRadius 8, `.font(.body)`) — identical to Tag and Group detail headers. (An earlier draft prescribed an Icon-1 / Header-1 header for Locations; removed when the app standardized on the single 36×36 collection style.)

### Dashboard Contact Rows (Fading Relationships, Strongest Relationships, and future dashboard cards)

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ Pods/
# IDE
.idea/
*.code-workspace
.claude/scheduled_tasks.lock
4 changes: 0 additions & 4 deletions Blackbook.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@
77D2B599AE22B4597BEBEEF8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A80CFB6F6BA1BDFBDE29CBD4 /* Assets.xcassets */; };
79377F20F80D93BE4ED30678 /* LocationModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47D954BD6E7D82202705588 /* LocationModelTests.swift */; };
7D4533D903ED240ABB1F8386 /* Reminder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 367F11DDCEA44032C3674833 /* Reminder.swift */; };
7EAE471A3C0DEAE6F297474D /* LocalSyncServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E45E53C6F0C7F7A6808F6EE /* LocalSyncServer.swift */; };
7FD59E02711302FDE3365206 /* ContactLocationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6331C25944672EB2F32005C /* ContactLocationPickerView.swift */; };
819102BFD7CD40272EA76C97 /* InteractionViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 367B4A2B6BD57C86106A9103 /* InteractionViewModelTests.swift */; };
82E24EDF90B4FDD321E60DC3 /* AuthGateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFF1714EC74CED7F5A86E00E /* AuthGateView.swift */; };
Expand Down Expand Up @@ -209,7 +208,6 @@
4AB554426E75CDDDB20498E2 /* ServerModelContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerModelContainer.swift; sourceTree = "<group>"; };
4C58C1200FB5DE674799040E /* AIInsightsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIInsightsView.swift; sourceTree = "<group>"; };
4DCA116B785721C8942FD325 /* FeatureGatingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureGatingTests.swift; sourceTree = "<group>"; };
4E45E53C6F0C7F7A6808F6EE /* LocalSyncServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalSyncServer.swift; sourceTree = "<group>"; };
4FC80EA436D4FD929A77680C /* console.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = console.html; sourceTree = "<group>"; };
530F5990544546A519FE2390 /* IMessageSyncService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IMessageSyncService.swift; sourceTree = "<group>"; };
535A0087C38A75EF251D28DF /* DeviceIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceIdentity.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -436,7 +434,6 @@
DC159C3515CC1B41D178A551 /* ContactSyncService.swift */,
2382E25FE6BDF339DA44560D /* GoogleCalendarService.swift */,
D876B79CE3D85BAABBEBD128 /* LocalServerSyncService.swift */,
4E45E53C6F0C7F7A6808F6EE /* LocalSyncServer.swift */,
682862BCC7F0F257DF35CEB8 /* NetworkGraphEngine.swift */,
1D046FD999BC660D58C26E32 /* PhotoStorageService.swift */,
56EBAC598F8585225B1F09E4 /* RelationshipScoreEngine.swift */,
Expand Down Expand Up @@ -838,7 +835,6 @@
A83BFC0E65269E8E57FE2EF7 /* KeychainService.swift in Sources */,
9A18735D7C1D616279A77B5E /* LocalServerSyncService.swift in Sources */,
AC83AF34CB61E8BE2848B4F1 /* LocalSyncProtocol.swift in Sources */,
7EAE471A3C0DEAE6F297474D /* LocalSyncServer.swift in Sources */,
480F4AB91090F3D31E17B0D2 /* Location.swift in Sources */,
74A5E0E803230F6D72D2E75B /* LocationDetailView.swift in Sources */,
D1D5EC721BB0E44AF9636690 /* LocationIconSuggestionView.swift in Sources */,
Expand Down
Loading
Loading