Skip to content

Make Subscription.LastSeenAt optional (*time.Time)#151

Merged
mliu33 merged 3 commits intomainfrom
claude/optional-lastseenAt-field-FJGwl
May 5, 2026
Merged

Make Subscription.LastSeenAt optional (*time.Time)#151
mliu33 merged 3 commits intomainfrom
claude/optional-lastseenAt-field-FJGwl

Conversation

@saurav-err-137
Copy link
Copy Markdown
Collaborator

@saurav-err-137 saurav-err-137 commented May 4, 2026

Summary

Changes Subscription.LastSeenAt from a required time.Time field to an optional *time.Time field, allowing it to be nil when a subscription has not yet been viewed. This aligns with the existing pattern used by HistorySharedSince in the same struct and ThreadSubscription.LastSeenAt.

Changes

  • pkg/model/subscription.go: Changed LastSeenAt field type from time.Time to *time.Time with omitempty tags on both JSON and BSON, matching the pattern of HistorySharedSince
  • pkg/model/model_test.go: Updated TestSubscriptionJSON to:
    • Create a pointer to the LastSeenAt timestamp in the "with optional fields set" sub-test
    • Add a new sub-test verifying that lastSeenAt is omitted from JSON when nil
    • Verify round-trip serialization/deserialization works correctly in both cases
    • Use require assertions for cleaner test code

Implementation Details

  • No production code currently reads Subscription.LastSeenAt, so this is a pure compile-time change at the model layer
  • The four subscription creation sites continue to work unchanged since they never explicitly set this field
  • When nil, the field is dropped from both JSON output and BSON documents via the omitempty tags
  • No data migration required; existing zero-valued timestamps in MongoDB will deserialize as non-nil pointers to the zero time

https://claude.ai/code/session_01BBCq6GLGp2wdYua9Hbub3Y

Summary by CodeRabbit

  • Documentation

    • Added a design spec describing making subscription timestamp fields optional.
  • New Features

    • Subscription timestamp field can now be omitted from API responses when unset; behavior is backward-compatible and requires no data migration.
  • Tests

    • Updated serialization tests to cover nil/omitted timestamp round-trips.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

📝 Walkthrough

Walkthrough

Makes model.Subscription.LastSeenAt optional by changing its type from time.Time to *time.Time with omitempty JSON/BSON tags, adds a design doc, and updates tests to verify non-nil and nil behaviors.

Changes

Subscription LastSeenAt Optionality

Layer / File(s) Summary
Design Specification
docs/superpowers/specs/2026-04-30-optional-lastseenat-design.md
Adds a spec describing making Subscription.LastSeenAt a *time.Time with omitempty, legacy BSON decoding behavior, test updates, and rollout/verification notes.
Core Data Shape
pkg/model/subscription.go
Changes Subscription.LastSeenAt from time.Time to *time.Time and adds json:"lastSeenAt,omitempty" bson:"lastSeenAt,omitempty".
Tests
pkg/model/model_test.go
Refactors TestSubscriptionJSON into two subtests: one ensures round-trip with a non-nil LastSeenAt; the other verifies lastSeenAt is omitted when nil, unmarshals to a nil pointer, and round-trips.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • mliu33
  • Joey0538

Poem

🐰 I nudged a timestamp into a pointer's nest,
Now nil can rest where zero once pressed.
Tests dance lightly, docs hum the tune—
A small clean change beneath the moon. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: making Subscription.LastSeenAt optional by converting it to a pointer type.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/optional-lastseenAt-field-FJGwl

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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
pkg/model/model_test.go (1)

400-406: ⚡ Quick win

Use the shared roundTrip helper for the equality path.

These two blocks reimplement the same marshal/unmarshal/deep-equal flow that roundTrip already centralizes at the bottom of this file. Keeping the lastSeenAt omission assertions here makes sense, but the round-trip part should stay on the helper for consistency with the rest of pkg/model/model_test.go.

As per coding guidelines, pkg/model/model_test.go must verify all domain types marshal/unmarshal correctly via a generic roundTrip helper.

Also applies to: 428-433

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/model/model_test.go` around lines 400 - 406, Replace the manual
json.Marshal/json.Unmarshal/reflect.DeepEqual sequence used to test
model.Subscription with the shared roundTrip helper: keep the assertions that
verify lastSeenAt is omitted, but remove the explicit
marshal/unmarshal/deep-equal block and instead call roundTrip with the
subscription value (and destination if your helper requires it) so the equality
path uses the centralized roundTrip helper; apply the same change to the other
similar block later in the file that checks Subscription round-tripping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/model/model_test.go`:
- Around line 400-406: Replace the manual
json.Marshal/json.Unmarshal/reflect.DeepEqual sequence used to test
model.Subscription with the shared roundTrip helper: keep the assertions that
verify lastSeenAt is omitted, but remove the explicit
marshal/unmarshal/deep-equal block and instead call roundTrip with the
subscription value (and destination if your helper requires it) so the equality
path uses the centralized roundTrip helper; apply the same change to the other
similar block later in the file that checks Subscription round-tripping.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ead07fbb-fc2b-499a-a25c-56954cc33606

📥 Commits

Reviewing files that changed from the base of the PR and between 1ddfcff and fc17a41.

📒 Files selected for processing (3)
  • docs/superpowers/specs/2026-04-30-optional-lastseenat-design.md
  • pkg/model/model_test.go
  • pkg/model/subscription.go

claude added 3 commits May 4, 2026 08:11
Change LastSeenAt from time.Time to *time.Time with omitempty tags so
"never seen" is represented as nil rather than the Go zero time. Mirrors
the pattern already used by HistorySharedSince in the same struct and by
ThreadSubscription.LastSeenAt.

No production code reads or writes the field today, so existing creation
sites continue to compile unchanged.
@saurav-err-137 saurav-err-137 force-pushed the claude/optional-lastseenAt-field-FJGwl branch from 0fece90 to 1193d07 Compare May 4, 2026 08:13
Copy link
Copy Markdown

@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: 1

🧹 Nitpick comments (1)
pkg/model/model_test.go (1)

411-431: ⚡ Quick win

Add BSON coverage for LastSeenAt nil-omission and non-nil round-trip.

The PR adds omitempty to the BSON tag (bson:"lastSeenAt,omitempty") — a behavioural change that affects MongoDB persistence (message-worker/store_mongo.go writes "lastSeenAt": sub.LastSeenAt). The new subtests only verify JSON omission; neither the nil-drops-field nor the non-nil-round-trips claim is exercised at the BSON level. The design spec explicitly states both behaviours, so a test locking them in is warranted.

✅ Suggested addition to TestSubscriptionJSON
+	t.Run("bson: lastSeenAt omitted when nil", func(t *testing.T) {
+		s := model.Subscription{
+			ID:       "s1",
+			User:     model.SubscriptionUser{ID: "u1", Account: "alice"},
+			RoomID:   "r1",
+			RoomType: model.RoomTypeChannel,
+			SiteID:   "site-a",
+			Roles:    []model.Role{model.RoleMember},
+			JoinedAt: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC),
+		}
+		data, err := bson.Marshal(&s)
+		require.NoError(t, err)
+		var raw bson.M
+		require.NoError(t, bson.Unmarshal(data, &raw))
+		_, present := raw["lastSeenAt"]
+		assert.False(t, present, "lastSeenAt should be omitted from BSON when nil")
+	})
+
+	t.Run("bson: lastSeenAt round-trips when set", func(t *testing.T) {
+		lsa := time.Date(2026, 1, 2, 0, 0, 0, 0, time.UTC)
+		s := model.Subscription{
+			ID:       "s1",
+			User:     model.SubscriptionUser{ID: "u1", Account: "alice"},
+			RoomID:   "r1",
+			RoomType: model.RoomTypeChannel,
+			SiteID:   "site-a",
+			Roles:    []model.Role{model.RoleMember},
+			JoinedAt: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC),
+			LastSeenAt: &lsa,
+		}
+		data, err := bson.Marshal(&s)
+		require.NoError(t, err)
+		var dst model.Subscription
+		require.NoError(t, bson.Unmarshal(data, &dst))
+		require.NotNil(t, dst.LastSeenAt)
+		assert.Equal(t, lsa.UTC(), dst.LastSeenAt.UTC())
+	})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/model/model_test.go` around lines 411 - 431, Add BSON-level assertions
for Subscription.LastSeenAt in the TestSubscriptionJSON tests: for the nil case,
marshal the Subscription with bson.Marshal, unmarshal into map[string]any (or
bson.M) and assert "lastSeenAt" is absent; for the non-nil case, set LastSeenAt,
bson.Marshal then bson.Unmarshal back into a Subscription and assert LastSeenAt
round-trips equal. Use the existing Subscription type, the test helper roundTrip
pattern, and the bson package (e.g., bson.Marshal/bson.Unmarshal or bson.M) to
mirror the JSON tests so the bson:"lastSeenAt,omitempty" behavior is covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/superpowers/specs/2026-04-30-optional-lastseenat-design.md`:
- Around line 20-22: The doc's assertion is wrong: ThreadSubscription.LastSeenAt
is a *time.Time pointer without `omitempty` (see the architectural note that
BSON nil must be encoded as explicit null); update the text to state that
Subscription should mirror the pointer pattern of ThreadSubscription (i.e., use
*time.Time to represent "never seen") but not apply `omitempty` there, and
clarify that only HistorySharedSince is intended to align with `omitempty`.
Reference ThreadSubscription.LastSeenAt, Subscription, and HistorySharedSince in
the corrected sentence so the distinction and rationale are unambiguous.

---

Nitpick comments:
In `@pkg/model/model_test.go`:
- Around line 411-431: Add BSON-level assertions for Subscription.LastSeenAt in
the TestSubscriptionJSON tests: for the nil case, marshal the Subscription with
bson.Marshal, unmarshal into map[string]any (or bson.M) and assert "lastSeenAt"
is absent; for the non-nil case, set LastSeenAt, bson.Marshal then
bson.Unmarshal back into a Subscription and assert LastSeenAt round-trips equal.
Use the existing Subscription type, the test helper roundTrip pattern, and the
bson package (e.g., bson.Marshal/bson.Unmarshal or bson.M) to mirror the JSON
tests so the bson:"lastSeenAt,omitempty" behavior is covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 29ea5c1c-a253-4dfc-8fbf-d2390e3557b9

📥 Commits

Reviewing files that changed from the base of the PR and between fc17a41 and 1193d07.

📒 Files selected for processing (3)
  • docs/superpowers/specs/2026-04-30-optional-lastseenat-design.md
  • pkg/model/model_test.go
  • pkg/model/subscription.go
✅ Files skipped from review due to trivial changes (1)
  • pkg/model/subscription.go

Comment on lines +20 to +22
- The codebase already models the analogous "never seen" state on
`ThreadSubscription` as `*time.Time` with `omitempty`. Subscription should
follow the same convention.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Inaccurate claim about ThreadSubscription.LastSeenAt using omitempty.

ThreadSubscription.LastSeenAt is *time.Time without omitempty — and its source file carries an explicit architectural note:

"Never add omitempty: unreadThreadsPipeline relies on BSON encoding nil as explicit null, not a missing field."

This sentence should clarify that Subscription only mirrors the *time.Time pointer pattern from ThreadSubscription, while the omitempty alignment is specifically with HistorySharedSince.

📝 Suggested correction
-The codebase already models the analogous "never seen" state on
-`ThreadSubscription` as `*time.Time` with `omitempty`. Subscription should
-follow the same convention.
+The codebase already models the analogous "never seen" state on
+`ThreadSubscription` as `*time.Time` (though without `omitempty` there, as the
+pipeline relies on an explicit BSON null). For `Subscription`, both the pointer
+type and the `omitempty` behaviour mirror the sibling `HistorySharedSince` field.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- The codebase already models the analogous "never seen" state on
`ThreadSubscription` as `*time.Time` with `omitempty`. Subscription should
follow the same convention.
- The codebase already models the analogous "never seen" state on
`ThreadSubscription` as `*time.Time` (though without `omitempty` there, as the
pipeline relies on an explicit BSON null). For `Subscription`, both the pointer
type and the `omitempty` behaviour mirror the sibling `HistorySharedSince` field.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/specs/2026-04-30-optional-lastseenat-design.md` around lines
20 - 22, The doc's assertion is wrong: ThreadSubscription.LastSeenAt is a
*time.Time pointer without `omitempty` (see the architectural note that BSON nil
must be encoded as explicit null); update the text to state that Subscription
should mirror the pointer pattern of ThreadSubscription (i.e., use *time.Time to
represent "never seen") but not apply `omitempty` there, and clarify that only
HistorySharedSince is intended to align with `omitempty`. Reference
ThreadSubscription.LastSeenAt, Subscription, and HistorySharedSince in the
corrected sentence so the distinction and rationale are unambiguous.

Copy link
Copy Markdown
Collaborator

@mliu33 mliu33 left a comment

Choose a reason for hiding this comment

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

Thanks!

@mliu33 mliu33 merged commit 1834359 into main May 5, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants