Skip to content

purple: add scalable tenure star badges (1–10 stars)#3592

Open
alltheseas wants to merge 2 commits intodamus-io:masterfrom
alltheseas:elsat/three-stars
Open

purple: add scalable tenure star badges (1–10 stars)#3592
alltheseas wants to merge 2 commits intodamus-io:masterfrom
alltheseas:elsat/three-stars

Conversation

@alltheseas
Copy link
Copy Markdown
Collaborator

@alltheseas alltheseas commented Feb 4, 2026

Commits (review commit-by-commit)

  1. b9c1ecfe — purple: replace boolean tenure attributes with int duration (+23 impl, +89 test)
  2. b2d6d350 — purple: generalize star badges to support 1–10 stars (+90 impl, +22 test)

Total: 113 impl, 111 test

Summary

Replace individual boolean tenure flags (member_for_more_than_one_year) with a single active_membership_duration integer (seconds) from the API. Badge tier is now computed client-side: one star per year of membership, up to 10.

Commit 1 swaps the PurpleAccountAttributes OptionSet for a TimeInterval property, with backward-compatible decoding (falls back to the old boolean when the duration field is absent).

Commit 2 replaces the hardcoded DoubleStar / BadgeVariant enum with a generalized MultiStar(count:) view that handles any star count via path operations (iOS 17+) with an iOS 16 fallback.

Companion PR

API server: damus-io/api#27

Checklist

Standard PR Checklist

  • I have read (or I am familiar with) the Contribution Guidelines
  • I have tested the changes in this PR
  • I have profiled the changes to ensure there are no performance regressions, or I do not need to profile the changes.
    • If not needed, provide reason: UI-only change with no compute-intensive operations
  • I have opened or referred to an existing github issue related to this change.
  • My PR is either small, or I have split it into smaller logical commits that are easier to review
  • I have added the signoff line to all my commits. See Signing off your work
  • I have added appropriate changelog entries for the changes in this PR. See Adding changelog entries
  • I have added appropriate Closes: or Fixes: tags in the commit messages wherever applicable, or made sure those are not needed. See Submitting patches

Test report

Device: iPhone 17 Pro Simulator

iOS: 26.2

Damus: This branch

Setup: Built and ran tests via xcodebuild. Manually tested 1, 2, 3, 4, 5, and 10 star scenarios on simulator.

Steps:

  1. Ran DamusPurpleAccountAttributesTests — 5 tests covering decoding and star_count logic
  2. Verified each commit builds and passes tests independently
  3. Visually tested star rendering at 1, 2, 3, 4, 5, and 10 stars on simulator

Results:

  • PASS

Other notes

The Purple API server needs to return active_membership_duration (seconds) in the attributes payload. See companion PR above. The client falls back to the old member_for_more_than_one_year boolean for backward compatibility with older server versions.

Closes: #3591

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Parses server flags into extended PurpleAccountAttributes (adds three‑year flag), selects a triple‑star supporter badge variant with accessibility labels, adds unit tests for attribute parsing, and updates Xcode project entries including a Swift package reference rename and test file inclusion.

Changes

Cohort / File(s) Summary
Project Configuration
damus.xcodeproj/project.pbxproj
Added DamusPurpleAccountAttributesTests.swift to project/test build entries; renamed XCRemoteSwiftPackageReference occurrences from secp256k1secp256k1.swift and updated related product dependency references.
Account Attributes Model
damus/Features/Purple/Models/DamusPurple.swift
Reworked PurpleAccountAttributes flags: removed single-year-only derivation, added memberForMoreThanThreeYears, updated Payload.Attributes to include member_for_more_than_one_year: Bool and member_for_more_than_three_years: Bool?, and updated Account.from(payload:) to compute attributes from server flags.
Badge Component
damus/Shared/Components/SupporterBadge.swift
Added BadgeVariant.threeYearSpecial, introduced TripleStar view, updated badge selection to prefer three‑year variant, and added accessibilityDescription/accessibilityLabel variants.
Test Suite
damusTests/DamusPurpleAccountAttributesTests.swift
New tests verifying parsing of no attributes, one‑year, three‑year (and combined), null attributes, and absent attributes cases for DamusPurple.Account.from(json_data:).

Sequence Diagram(s)

sequenceDiagram
    participant Server as Server
    participant AccountModel as Account.Model
    participant Badge as SupporterBadge
    participant View as BadgeView

    Server->>AccountModel: send JSON payload with attributes
    activate AccountModel
    AccountModel->>AccountModel: parse payload into Payload.Attributes
    AccountModel->>AccountModel: compute PurpleAccountAttributes (one‑year, three‑year)
    AccountModel-->>Badge: provide Account with attributes
    deactivate AccountModel

    activate Badge
    Badge->>Badge: evaluate memberForMoreThanThreeYears?
    alt three-year
        Badge->>View: render TripleStar
    else one-year
        Badge->>View: render one-year special
    else none
        Badge->>View: render normal badge
    end
    Badge->>View: set accessibilityLabel (variant-specific)
    deactivate Badge
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I parsed the purple flags tonight,
Three stars ready, shining bright.
JSON nibbled, attributes found,
Triple‑star hops on proud new ground —
A rabbit cheers for badges right!

🚥 Pre-merge checks | ✅ 3 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (11 files):

⚔️ damus.xcodeproj/project.pbxproj (content)
⚔️ damus/ContentView.swift (content)
⚔️ damus/Core/Nostr/RelayPool.swift (content)
⚔️ damus/Features/Events/NoteContentView.swift (content)
⚔️ damus/Features/Purple/Models/DamusPurple.swift (content)
⚔️ damus/Features/Timeline/Models/HomeModel.swift (content)
⚔️ damus/Features/Timeline/Views/PostingTimelineView.swift (content)
⚔️ damus/Features/Timeline/Views/TimelineView.swift (content)
⚔️ damus/Shared/Components/SupporterBadge.swift (content)
⚔️ damus/Shared/Utilities/EventCache.swift (content)
⚔️ nostrdb/NdbNote.swift (content)

These conflicts must be resolved before merging into master.
Resolve conflicts locally and push changes to this branch.
Title check ⚠️ Warning The PR title states 'purple: add scalable tenure star badges (1–10 stars)' but the actual implementation only adds support for three-year badges (triple star), not a scalable 1-10 star system. Revise the title to accurately reflect the scope, such as 'purple: add triple star badge for three-year members' to match the actual implementation.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR addresses the linked issue #3591 (three star anniversary) by implementing triple star badge display for three-year Purple members with supporting attributes and tests.
Out of Scope Changes check ✅ Passed All changes are directly related to the three-year badge feature: model updates, UI components, tests, and project file updates for the new test file and package reference rename.
Description check ✅ Passed The PR description includes a summary, complete standard checklist with all items checked, comprehensive test report with device/OS/setup details, and notes explaining API dependencies.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch elsat/three-stars
  • Post resolved changes as copyable diffs in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@alltheseas
Copy link
Copy Markdown
Collaborator Author

requires Purple API change: #3591 (comment)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@damus.xcodeproj/project.pbxproj`:
- Around line 1500-1504: Remove or fix the dangling PBXBuildFile entry
D73E5EFE2C6A97F4007EB227 in the Sources build phase: either delete this build
file record (the one with isa = PBXBuildFile and no fileRef) from the
project.pbxproj and from the Sources phase list, or replace its missing fileRef
with the correct file reference to the intended source file; ensure no other
build phase entries reference the same placeholder and repeat the same cleanup
for the other occurrences noted (e.g., the similar entries around lines
7262-7266).

In `@damus/Features/Purple/Models/DamusPurple.swift`:
- Around line 474-478: Add a doc comment for the new optional field
member_for_more_than_three_years in the Attributes struct explaining what the
flag represents (e.g., true when the member has been a member for more than
three years), that it is optional/nullable, and any server semantics (when it
may be omitted); update the Attributes struct's property comments so both
member_for_more_than_one_year and member_for_more_than_three_years have concise
docstrings describing their meaning and types.

In `@damus/Shared/Components/SupporterBadge.swift`:
- Around line 176-251: Add doc comments to the newly introduced TripleStar view
internals: document the TripleStar struct (describe purpose and parameters like
size and starOffset), the nested TripleStarShape (explain strokeSize, starOffset
and what path(in:) returns), and the Fallback view (describe its behavior for
iOS <17 and parameters size and starOffset). Place concise /// comments above
TripleStar, TripleStarShape, TripleStarShape.path(in:), and Fallback explaining
intent, parameters, and platform behavior to satisfy docstring coverage
guidelines.
🧹 Nitpick comments (2)
damusTests/DamusPurpleAccountAttributesTests.swift (2)

88-110: Consider consolidating or differentiating this test.

testParseAccountWithMissingThreeYearField has an identical JSON payload to testParseAccountWithOneYearAttribute (lines 40-52). While the intent differs (feature verification vs backward compatibility documentation), the tests are functionally redundant.

Consider either:

  1. Consolidating them into a single test with clear documentation of both intents, or
  2. Adding a distinguishing comment to clarify why both exist.

112-133: Consider adding a test for absent attributes key.

The current tests cover "attributes": null, but there's no test for when the attributes key is completely absent from the JSON. Depending on how Payload.Attributes is declared, these may behave differently during JSON decoding.

Suggested additional test
/// Tests backward compatibility when the attributes field is absent from the response.
func testParseAccountWithAbsentAttributesField() throws {
    let json = """
    {
        "pubkey": "\(testPubkeyHex)",
        "created_at": 1700000000,
        "expiry": 1800000000,
        "subscriber_number": 42,
        "active": true
    }
    """

    guard let account = DamusPurple.Account.from(json_data: json.data(using: .utf8)!) else {
        XCTFail("Failed to parse account")
        return
    }

    XCTAssertFalse(account.attributes.contains(.memberForMoreThanOneYear))
    XCTAssertFalse(account.attributes.contains(.memberForMoreThanThreeYears))
}

@alltheseas alltheseas added the purple Damus purple membership label Feb 4, 2026
@alltheseas alltheseas force-pushed the elsat/three-stars branch 2 times, most recently from c03fd23 to 919c474 Compare February 5, 2026 00:01
@alltheseas
Copy link
Copy Markdown
Collaborator Author

Addressed coderabbit nitpick comments:

  1. Removed redundant test - testParseAccountWithMissingThreeYearField was identical to testParseAccountWithOneYearAttribute, so I removed it. The backward compatibility for missing fields is implicitly tested by testParseAccountWithOneYearAttribute since the member_for_more_than_three_years field is absent in that JSON.

  2. Added test for absent attributes key - Added testParseAccountWithAbsentAttributesKey which tests when the attributes key is completely missing from the JSON (not just null).

All 5 tests pass.

@alltheseas alltheseas mentioned this pull request Feb 13, 2026
30 tasks
alltheseas added a commit to alltheseas/api that referenced this pull request Feb 13, 2026
Return the new member_for_more_than_three_years boolean in the account
attributes response. Uses the same 360-day-year convention as the
existing one-year check (3 × 360 = 1080 days). The total membership
time is now computed once and reused for both thresholds.

Companion to damus-io/damus#3592.

Signed-off-by: alltheseas <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
@alltheseas
Copy link
Copy Markdown
Collaborator Author

Companion API PR: damus-io/api#27 — adds the member_for_more_than_three_years field to the account attributes response so this feature works end-to-end.

alltheseas added a commit to alltheseas/api that referenced this pull request Feb 13, 2026
Return the new member_for_more_than_three_years boolean in the account
attributes response. Uses the same 360-day-year convention as the
existing one-year check (3 × 360 = 1080 days). The total membership
time is now computed once and reused for both thresholds.

Companion to damus-io/damus#3592.

Signed-off-by: alltheseas <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
@alltheseas alltheseas force-pushed the elsat/three-stars branch 2 times, most recently from 944d7da to 0ea39fd Compare February 13, 2026 20:26
@alltheseas
Copy link
Copy Markdown
Collaborator Author

Closing in favor of consolidated Linux-style topic branches. This work will be audited against AGENTS.md requirements and cherry-picked into ordered topic branches. See #3624 for tracking.

alltheseas and others added 2 commits February 19, 2026 20:55
Replace the PurpleAccountAttributes OptionSet with a single
active_membership_duration TimeInterval (seconds) from the API.

Backward compatible: falls back to the member_for_more_than_one_year
boolean when the duration field is absent, and handles null/missing
attributes gracefully.

Closes: damus-io#3591
Changelog-Added: Add tenure star badges for Purple members (1 star per year, up to 10)
Signed-off-by: elsat <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Replace DoubleStar and BadgeVariant enum with a single MultiStar(count:)
view driven by a star_count computed property (1 per year, capped at 10).

MultiStarShape uses a loop over path operations (iOS 17+) with an
iOS 16 fallback using ForEach. Adds accessibility labels.

Signed-off-by: elsat <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
@alltheseas
Copy link
Copy Markdown
Collaborator Author

I added up to ten (10) stars.

Screenshot 2026-02-19 at 8 29 52 PM Screenshot 2026-02-19 at 8 32 27 PM Screenshot 2026-02-19 at 8 35 49 PM Screenshot 2026-02-19 at 8 38 06 PM

@alltheseas alltheseas requested a review from jb55 February 20, 2026 03:02
@alltheseas alltheseas changed the title Add triple star badge for three-year Purple members purple: add scalable tenure star badges (1–10 stars) Feb 20, 2026
Copy link
Copy Markdown
Collaborator

@danieldaquino danieldaquino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10 stars seems a bit too much. @jb55, is this desired behaviour? Can you please confirm your new requirements?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

purple Damus purple membership

Projects

None yet

Development

Successfully merging this pull request may close these issues.

three star anniversary

3 participants