refactor(client): move builders to Client methods

Amolith created

Builders are now created via client.NewX() instead of lunatask.NewX(),
and Create/Update methods no longer require a client parameter.

Before: lunatask.NewTask("x").Create(ctx, client) After:
client.NewTask("x").Create(ctx)

Assisted-by: Claude Sonnet 4 via Crush

Change summary

AGENTS.md        |  2 +-
README.md        |  4 ++--
journal.go       | 15 ++++++++-------
journal_test.go  | 10 +++++-----
notes.go         | 28 +++++++++++++++-------------
notes_test.go    | 20 ++++++++++----------
people.go        | 18 +++++++++++-------
people_test.go   | 14 +++++++-------
tasks.go         | 28 +++++++++++++++-------------
tasks_test.go    | 24 ++++++++++++------------
timeline.go      | 15 ++++++++-------
timeline_test.go | 10 +++++-----
12 files changed, 99 insertions(+), 89 deletions(-)

Detailed changes

AGENTS.md 🔗

@@ -24,7 +24,7 @@ task                            # Include vulnerability and copyright checks
 Create methods return `(nil, nil)` when a matching entity already exists (HTTP 204). This is intentional API behavior, not an error:
 
 ```go
-task, err := lunatask.NewTask("Review PR").Create(ctx, client)
+task, err := client.NewTask("Review PR").Create(ctx)
 if err != nil {
     return err // actual error
 }

README.md 🔗

@@ -53,7 +53,7 @@ func main() {
 	}
 
 	// Create a task
-	task, err := lunatask.NewTask("Review pull requests").Create(context.Background(), client)
+	task, err := client.NewTask("Review pull requests").Create(context.Background())
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -70,7 +70,7 @@ exists. This is intentional API behavior on Lunatask's part because of
 its end-to-end encryption.
 
 ```go
-task, err := lunatask.NewTask("Review PR").Create(ctx, client)
+task, err := client.NewTask("Review PR").Create(ctx)
 if err != nil {
     return err // actual error
 }

journal.go 🔗

@@ -39,16 +39,17 @@ type journalEntryResponse struct {
 // Journal content is encrypted client-side; the API accepts it on create but
 // returns null on read.
 //
-//	entry, err := lunatask.NewJournalEntry(lunatask.Today()).
+//	entry, err := client.NewJournalEntry(lunatask.Today()).
 //		WithContent("Shipped the new feature!").
-//		Create(ctx, client)
+//		Create(ctx)
 type JournalEntryBuilder struct {
-	req createJournalEntryRequest
+	client *Client
+	req    createJournalEntryRequest
 }
 
 // NewJournalEntry starts building a journal entry for the given date.
-func NewJournalEntry(date Date) *JournalEntryBuilder {
-	return &JournalEntryBuilder{req: createJournalEntryRequest{DateOn: date}} //nolint:exhaustruct
+func (c *Client) NewJournalEntry(date Date) *JournalEntryBuilder {
+	return &JournalEntryBuilder{client: c, req: createJournalEntryRequest{DateOn: date}}
 }
 
 // WithName sets the entry's title. Defaults to the weekday name if omitted.
@@ -67,12 +68,12 @@ func (b *JournalEntryBuilder) WithContent(content string) *JournalEntryBuilder {
 
 // Create sends the journal entry to Lunatask. Returns the created entry's metadata;
 // Name and Content won't round-trip due to E2EE.
-func (b *JournalEntryBuilder) Create(ctx context.Context, c *Client) (*JournalEntry, error) {
+func (b *JournalEntryBuilder) Create(ctx context.Context) (*JournalEntry, error) {
 	if b.req.DateOn.IsZero() {
 		return nil, fmt.Errorf("%w: date_on is required", ErrBadRequest)
 	}
 
-	resp, _, err := doJSON[journalEntryResponse](ctx, c, http.MethodPost, "/journal_entries", b.req)
+	resp, _, err := doJSON[journalEntryResponse](ctx, b.client, http.MethodPost, "/journal_entries", b.req)
 	if err != nil {
 		return nil, err
 	}

journal_test.go 🔗

@@ -23,9 +23,9 @@ func TestCreateJournalEntry_Success(t *testing.T) {
 
 	entryDate := lunatask.NewDate(time.Date(2021, 1, 10, 0, 0, 0, 0, time.UTC))
 
-	entry, err := lunatask.NewJournalEntry(entryDate).
+	entry, err := client.NewJournalEntry(entryDate).
 		WithContent("Today was a tough day, but on the other side...").
-		Create(ctx(), client)
+		Create(ctx())
 	if err != nil {
 		t.Fatalf("error = %v", err)
 	}
@@ -52,10 +52,10 @@ func TestCreateJournalEntry_WithName(t *testing.T) {
 
 	entryDate := lunatask.NewDate(time.Date(2021, 1, 10, 0, 0, 0, 0, time.UTC))
 
-	_, err := lunatask.NewJournalEntry(entryDate).
+	_, err := client.NewJournalEntry(entryDate).
 		WithName("Custom Title").
 		WithContent("Some content").
-		Create(ctx(), client)
+		Create(ctx())
 	if err != nil {
 		t.Fatalf("error = %v", err)
 	}
@@ -70,7 +70,7 @@ func TestCreateJournalEntry_Errors(t *testing.T) {
 
 	testErrorCases(t, func(c *lunatask.Client) error {
 		entryDate := lunatask.NewDate(time.Date(2021, 1, 10, 0, 0, 0, 0, time.UTC))
-		_, err := lunatask.NewJournalEntry(entryDate).Create(ctx(), c)
+		_, err := c.NewJournalEntry(entryDate).Create(ctx())
 
 		return err //nolint:wrapcheck // test helper
 	})

notes.go 🔗

@@ -84,18 +84,19 @@ func (c *Client) DeleteNote(ctx context.Context, noteID string) (*Note, error) {
 // Note fields are encrypted client-side by Lunatask; the API accepts them
 // on create but returns null on read.
 //
-//	note, err := lunatask.NewNote().
+//	note, err := client.NewNote().
 //		WithName("Meeting notes").
 //		WithContent("# Summary\n\n...").
 //		InNotebook(notebookID).
-//		Create(ctx, client)
+//		Create(ctx)
 type NoteBuilder struct {
-	req createNoteRequest
+	client *Client
+	req    createNoteRequest
 }
 
 // NewNote starts building a note.
-func NewNote() *NoteBuilder {
-	return &NoteBuilder{} //nolint:exhaustruct
+func (c *Client) NewNote() *NoteBuilder {
+	return &NoteBuilder{client: c}
 }
 
 // WithName sets the note's title.
@@ -130,24 +131,25 @@ func (b *NoteBuilder) FromSource(source, sourceID string) *NoteBuilder {
 
 // Create sends the note to Lunatask. Returns (nil, nil) if a duplicate exists
 // in the same notebook with matching source/source_id.
-func (b *NoteBuilder) Create(ctx context.Context, c *Client) (*Note, error) {
-	return create(ctx, c, "/notes", b.req, func(r noteResponse) Note { return r.Note })
+func (b *NoteBuilder) Create(ctx context.Context) (*Note, error) {
+	return create(ctx, b.client, "/notes", b.req, func(r noteResponse) Note { return r.Note })
 }
 
 // NoteUpdateBuilder constructs and updates a note via method chaining.
 // Only fields you set will be modified; others remain unchanged.
 //
-//	note, err := lunatask.NewNoteUpdate(noteID).
+//	note, err := client.NewNoteUpdate(noteID).
 //		WithContent("# Updated content").
-//		Update(ctx, client)
+//		Update(ctx)
 type NoteUpdateBuilder struct {
+	client *Client
 	noteID string
 	req    updateNoteRequest
 }
 
 // NewNoteUpdate starts building a note update for the given note ID.
-func NewNoteUpdate(noteID string) *NoteUpdateBuilder {
-	return &NoteUpdateBuilder{noteID: noteID} //nolint:exhaustruct
+func (c *Client) NewNoteUpdate(noteID string) *NoteUpdateBuilder {
+	return &NoteUpdateBuilder{client: c, noteID: noteID}
 }
 
 // WithName sets the note's title.
@@ -179,6 +181,6 @@ func (b *NoteUpdateBuilder) OnDate(date Date) *NoteUpdateBuilder {
 }
 
 // Update sends the changes to Lunatask.
-func (b *NoteUpdateBuilder) Update(ctx context.Context, c *Client) (*Note, error) {
-	return update(ctx, c, "/notes", b.noteID, "note", b.req, func(r noteResponse) Note { return r.Note })
+func (b *NoteUpdateBuilder) Update(ctx context.Context) (*Note, error) {
+	return update(ctx, b.client, "/notes", b.noteID, "note", b.req, func(r noteResponse) Note { return r.Note })
 }

notes_test.go 🔗

@@ -133,12 +133,12 @@ func TestCreateNote_Success(t *testing.T) {
 
 	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
 
-	note, err := lunatask.NewNote().
+	note, err := client.NewNote().
 		WithName("My note").
 		WithContent("Note content").
 		InNotebook(notebookID).
 		FromSource("evernote", "ext-123").
-		Create(ctx(), client)
+		Create(ctx())
 	if err != nil {
 		t.Fatalf("error = %v", err)
 	}
@@ -168,11 +168,11 @@ func TestCreateNote_Duplicate(t *testing.T) {
 
 	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
 
-	note, err := lunatask.NewNote().
+	note, err := client.NewNote().
 		WithName("Duplicate").
 		InNotebook(notebookID).
 		FromSource("evernote", "dup-123").
-		Create(ctx(), client)
+		Create(ctx())
 	if err != nil {
 		t.Fatalf("error = %v, want nil", err)
 	}
@@ -186,7 +186,7 @@ func TestCreateNote_Errors(t *testing.T) {
 	t.Parallel()
 
 	testErrorCases(t, func(c *lunatask.Client) error {
-		_, err := lunatask.NewNote().WithName("x").Create(ctx(), c)
+		_, err := c.NewNote().WithName("x").Create(ctx())
 
 		return err //nolint:wrapcheck // test helper
 	})
@@ -202,10 +202,10 @@ func TestUpdateNote_Success(t *testing.T) {
 
 	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
 
-	note, err := lunatask.NewNoteUpdate(noteID).
+	note, err := client.NewNoteUpdate(noteID).
 		WithName("Updated name").
 		WithContent("New content").
-		Update(ctx(), client)
+		Update(ctx())
 	if err != nil {
 		t.Fatalf("error = %v", err)
 	}
@@ -227,12 +227,12 @@ func TestUpdateNote_AllFields(t *testing.T) {
 	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
 	dateOn := lunatask.NewDate(time.Date(2024, 5, 10, 0, 0, 0, 0, time.UTC))
 
-	_, err := lunatask.NewNoteUpdate(noteID).
+	_, err := client.NewNoteUpdate(noteID).
 		WithName("Full update").
 		WithContent("Full content").
 		InNotebook("new-notebook-id").
 		OnDate(dateOn).
-		Update(ctx(), client)
+		Update(ctx())
 	if err != nil {
 		t.Fatalf("error = %v", err)
 	}
@@ -247,7 +247,7 @@ func TestUpdateNote_Errors(t *testing.T) {
 	t.Parallel()
 
 	testErrorCases(t, func(c *lunatask.Client) error {
-		_, err := lunatask.NewNoteUpdate(noteID).WithName("x").Update(ctx(), c)
+		_, err := c.NewNoteUpdate(noteID).WithName("x").Update(ctx())
 
 		return err //nolint:wrapcheck // test helper
 	})

people.go 🔗

@@ -113,16 +113,20 @@ func (c *Client) DeletePerson(ctx context.Context, personID string) (*Person, er
 // Name fields are encrypted client-side; the API accepts them on create but
 // returns null on read.
 //
-//	person, err := lunatask.NewPerson("Ada", "Lovelace").
+//	person, err := client.NewPerson("Ada", "Lovelace").
 //		WithRelationshipStrength(lunatask.RelationshipCloseFriend).
-//		Create(ctx, client)
+//		Create(ctx)
 type PersonBuilder struct {
-	req createPersonRequest
+	client *Client
+	req    createPersonRequest
 }
 
 // NewPerson starts building a person entry with the given name.
-func NewPerson(firstName, lastName string) *PersonBuilder {
-	return &PersonBuilder{req: createPersonRequest{FirstName: &firstName, LastName: &lastName}} //nolint:exhaustruct
+func (c *Client) NewPerson(firstName, lastName string) *PersonBuilder {
+	return &PersonBuilder{
+		client: c,
+		req:    createPersonRequest{FirstName: &firstName, LastName: &lastName},
+	}
 }
 
 // WithRelationshipStrength categorizes the closeness of the relationship.
@@ -157,6 +161,6 @@ func (b *PersonBuilder) WithCustomField(key string, value any) *PersonBuilder {
 
 // Create sends the person to Lunatask.
 // Returns (nil, nil) if a duplicate exists with matching source/source_id.
-func (b *PersonBuilder) Create(ctx context.Context, c *Client) (*Person, error) {
-	return create(ctx, c, "/people", b.req, func(r personResponse) Person { return r.Person })
+func (b *PersonBuilder) Create(ctx context.Context) (*Person, error) {
+	return create(ctx, b.client, "/people", b.req, func(r personResponse) Person { return r.Person })
 }

people_test.go 🔗

@@ -134,10 +134,10 @@ func TestCreatePerson_Success(t *testing.T) {
 
 	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
 
-	person, err := lunatask.NewPerson("John", "Doe").
+	person, err := client.NewPerson("John", "Doe").
 		WithRelationshipStrength(lunatask.RelationshipBusiness).
 		FromSource("salesforce", "sf-123").
-		Create(ctx(), client)
+		Create(ctx())
 	if err != nil {
 		t.Fatalf("error = %v", err)
 	}
@@ -165,11 +165,11 @@ func TestCreatePerson_WithCustomFields(t *testing.T) {
 
 	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
 
-	_, err := lunatask.NewPerson("Ada", "Lovelace").
+	_, err := client.NewPerson("Ada", "Lovelace").
 		WithRelationshipStrength(lunatask.RelationshipCloseFriend).
 		WithCustomField("email", "ada@example.com").
 		WithCustomField("phone", "+1234567890").
-		Create(ctx(), client)
+		Create(ctx())
 	if err != nil {
 		t.Fatalf("error = %v", err)
 	}
@@ -191,9 +191,9 @@ func TestCreatePerson_Duplicate(t *testing.T) {
 
 	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
 
-	person, err := lunatask.NewPerson("Dup", "Person").
+	person, err := client.NewPerson("Dup", "Person").
 		FromSource("salesforce", "dup-123").
-		Create(ctx(), client)
+		Create(ctx())
 	if err != nil {
 		t.Fatalf("error = %v, want nil", err)
 	}
@@ -207,7 +207,7 @@ func TestCreatePerson_Errors(t *testing.T) {
 	t.Parallel()
 
 	testErrorCases(t, func(c *lunatask.Client) error {
-		_, err := lunatask.NewPerson("Test", "User").Create(ctx(), c)
+		_, err := c.NewPerson("Test", "User").Create(ctx())
 
 		return err //nolint:wrapcheck // test helper
 	})

tasks.go 🔗

@@ -107,18 +107,19 @@ func (c *Client) DeleteTask(ctx context.Context, taskID string) (*Task, error) {
 
 // TaskBuilder constructs and creates a task via method chaining.
 //
-//	task, err := lunatask.NewTask("Review PR").
+//	task, err := client.NewTask("Review PR").
 //		InArea(areaID).
 //		WithStatus(lunatask.StatusNext).
 //		WithEstimate(30).
-//		Create(ctx, client)
+//		Create(ctx)
 type TaskBuilder struct {
-	req createTaskRequest
+	client *Client
+	req    createTaskRequest
 }
 
 // NewTask starts building a task with the given name.
-func NewTask(name string) *TaskBuilder {
-	return &TaskBuilder{req: createTaskRequest{Name: name}} //nolint:exhaustruct
+func (c *Client) NewTask(name string) *TaskBuilder {
+	return &TaskBuilder{client: c, req: createTaskRequest{Name: name}}
 }
 
 // InArea assigns the task to an area. IDs are in the area's settings in the app.
@@ -205,25 +206,26 @@ func (b *TaskBuilder) FromSource(source, sourceID string) *TaskBuilder {
 
 // Create sends the task to Lunatask. Returns (nil, nil) if a not-completed
 // task already exists in the same area with matching source/source_id.
-func (b *TaskBuilder) Create(ctx context.Context, c *Client) (*Task, error) {
-	return create(ctx, c, "/tasks", b.req, func(r taskResponse) Task { return r.Task })
+func (b *TaskBuilder) Create(ctx context.Context) (*Task, error) {
+	return create(ctx, b.client, "/tasks", b.req, func(r taskResponse) Task { return r.Task })
 }
 
 // TaskUpdateBuilder constructs and updates a task via method chaining.
 // Only fields you set will be modified; others remain unchanged.
 //
-//	task, err := lunatask.NewTaskUpdate(taskID).
+//	task, err := client.NewTaskUpdate(taskID).
 //		WithStatus(lunatask.StatusCompleted).
 //		CompletedAt(time.Now()).
-//		Update(ctx, client)
+//		Update(ctx)
 type TaskUpdateBuilder struct {
+	client *Client
 	taskID string
 	req    updateTaskRequest
 }
 
 // NewTaskUpdate starts building a task update for the given task ID.
-func NewTaskUpdate(taskID string) *TaskUpdateBuilder {
-	return &TaskUpdateBuilder{taskID: taskID} //nolint:exhaustruct
+func (c *Client) NewTaskUpdate(taskID string) *TaskUpdateBuilder {
+	return &TaskUpdateBuilder{client: c, taskID: taskID}
 }
 
 // Name changes the task's name.
@@ -307,6 +309,6 @@ func (b *TaskUpdateBuilder) CompletedAt(t time.Time) *TaskUpdateBuilder {
 }
 
 // Update sends the changes to Lunatask.
-func (b *TaskUpdateBuilder) Update(ctx context.Context, c *Client) (*Task, error) {
-	return update(ctx, c, "/tasks", b.taskID, "task", b.req, func(r taskResponse) Task { return r.Task })
+func (b *TaskUpdateBuilder) Update(ctx context.Context) (*Task, error) {
+	return update(ctx, b.client, "/tasks", b.taskID, "task", b.req, func(r taskResponse) Task { return r.Task })
 }

tasks_test.go 🔗

@@ -165,10 +165,10 @@ func TestCreateTask_Success(t *testing.T) {
 
 	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
 
-	task, err := lunatask.NewTask("My task").
+	task, err := client.NewTask("My task").
 		InArea(areaID).
 		FromSource(sourceGitHub, sourceID123).
-		Create(ctx(), client)
+		Create(ctx())
 	if err != nil {
 		t.Fatalf("error = %v", err)
 	}
@@ -198,7 +198,7 @@ func TestCreateTask_AllBuilderFields(t *testing.T) {
 	scheduledDate := lunatask.NewDate(time.Date(2024, 3, 15, 0, 0, 0, 0, time.UTC))
 	completedTime := time.Date(2024, 3, 15, 14, 30, 0, 0, time.UTC)
 
-	_, err := lunatask.NewTask("Full task").
+	_, err := client.NewTask("Full task").
 		InArea(areaID).
 		InGoal(goalID).
 		WithNote("Some markdown note").
@@ -210,7 +210,7 @@ func TestCreateTask_AllBuilderFields(t *testing.T) {
 		ScheduledOn(scheduledDate).
 		CompletedAt(completedTime).
 		FromSource(sourceGitHub, sourceID123).
-		Create(ctx(), client)
+		Create(ctx())
 	if err != nil {
 		t.Fatalf("error = %v", err)
 	}
@@ -238,10 +238,10 @@ func TestCreateTask_Duplicate(t *testing.T) {
 
 	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
 
-	task, err := lunatask.NewTask("Duplicate task").
+	task, err := client.NewTask("Duplicate task").
 		InArea(areaID).
 		FromSource(sourceGitHub, sourceID123).
-		Create(ctx(), client)
+		Create(ctx())
 		// Per AGENTS.md: Create methods return (nil, nil) for duplicates
 	if err != nil {
 		t.Fatalf("error = %v, want nil", err)
@@ -256,7 +256,7 @@ func TestCreateTask_Errors(t *testing.T) {
 	t.Parallel()
 
 	testErrorCases(t, func(c *lunatask.Client) error {
-		_, err := lunatask.NewTask("Test").InArea(areaID).Create(ctx(), c)
+		_, err := c.NewTask("Test").InArea(areaID).Create(ctx())
 
 		return err //nolint:wrapcheck // test helper
 	})
@@ -272,11 +272,11 @@ func TestUpdateTask_Success(t *testing.T) {
 
 	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
 
-	task, err := lunatask.NewTaskUpdate(taskID).
+	task, err := client.NewTaskUpdate(taskID).
 		Name("Updated name").
 		WithStatus(lunatask.StatusCompleted).
 		WithMotivation(lunatask.MotivationMust).
-		Update(ctx(), client)
+		Update(ctx())
 	if err != nil {
 		t.Fatalf("error = %v", err)
 	}
@@ -304,7 +304,7 @@ func TestUpdateTask_AllBuilderFields(t *testing.T) {
 	scheduledDate := lunatask.NewDate(time.Date(2024, 6, 20, 0, 0, 0, 0, time.UTC))
 	completedTime := time.Date(2024, 6, 20, 16, 45, 0, 0, time.UTC)
 
-	_, err := lunatask.NewTaskUpdate(taskID).
+	_, err := client.NewTaskUpdate(taskID).
 		Name("Full update").
 		InArea(areaID).
 		InGoal("goal-id").
@@ -316,7 +316,7 @@ func TestUpdateTask_AllBuilderFields(t *testing.T) {
 		WithPriority(-1).
 		ScheduledOn(scheduledDate).
 		CompletedAt(completedTime).
-		Update(ctx(), client)
+		Update(ctx())
 	if err != nil {
 		t.Fatalf("error = %v", err)
 	}
@@ -338,7 +338,7 @@ func TestUpdateTask_Errors(t *testing.T) {
 	t.Parallel()
 
 	testErrorCases(t, func(c *lunatask.Client) error {
-		_, err := lunatask.NewTaskUpdate(taskID).Name("x").Update(ctx(), c)
+		_, err := c.NewTaskUpdate(taskID).Name("x").Update(ctx())
 
 		return err //nolint:wrapcheck // test helper
 	})

timeline.go 🔗

@@ -38,18 +38,19 @@ type personTimelineNoteResponse struct {
 // TimelineNoteBuilder constructs and creates a timeline note via method chaining.
 // Content is encrypted client-side; the API accepts it on create but returns null on read.
 //
-//	note, err := lunatask.NewTimelineNote(personID).
+//	note, err := client.NewTimelineNote(personID).
 //		OnDate(lunatask.Today()).
 //		WithContent("Had coffee, discussed the project.").
-//		Create(ctx, client)
+//		Create(ctx)
 type TimelineNoteBuilder struct {
-	req createPersonTimelineNoteRequest
+	client *Client
+	req    createPersonTimelineNoteRequest
 }
 
 // NewTimelineNote starts building a timeline note for the given person.
 // Get person IDs from [Client.ListPeople].
-func NewTimelineNote(personID string) *TimelineNoteBuilder {
-	return &TimelineNoteBuilder{req: createPersonTimelineNoteRequest{PersonID: personID}} //nolint:exhaustruct
+func (c *Client) NewTimelineNote(personID string) *TimelineNoteBuilder {
+	return &TimelineNoteBuilder{client: c, req: createPersonTimelineNoteRequest{PersonID: personID}}
 }
 
 // OnDate sets when this interaction occurred.
@@ -68,12 +69,12 @@ func (b *TimelineNoteBuilder) WithContent(content string) *TimelineNoteBuilder {
 
 // Create sends the timeline note to Lunatask.
 // Get person IDs from [Client.ListPeople].
-func (b *TimelineNoteBuilder) Create(ctx context.Context, c *Client) (*PersonTimelineNote, error) {
+func (b *TimelineNoteBuilder) Create(ctx context.Context) (*PersonTimelineNote, error) {
 	if b.req.PersonID == "" {
 		return nil, fmt.Errorf("%w: person_id is required", ErrBadRequest)
 	}
 
-	resp, _, err := doJSON[personTimelineNoteResponse](ctx, c, http.MethodPost, "/person_timeline_notes", b.req)
+	resp, _, err := doJSON[personTimelineNoteResponse](ctx, b.client, http.MethodPost, "/person_timeline_notes", b.req)
 	if err != nil {
 		return nil, err
 	}

timeline_test.go 🔗

@@ -23,10 +23,10 @@ func TestCreateTimelineNote_Success(t *testing.T) {
 
 	noteDate := lunatask.NewDate(time.Date(2021, 1, 10, 0, 0, 0, 0, time.UTC))
 
-	note, err := lunatask.NewTimelineNote(personID).
+	note, err := client.NewTimelineNote(personID).
 		OnDate(noteDate).
 		WithContent("Today we talked about ...").
-		Create(ctx(), client)
+		Create(ctx())
 	if err != nil {
 		t.Fatalf("error = %v", err)
 	}
@@ -53,8 +53,8 @@ func TestCreateTimelineNote_MinimalFields(t *testing.T) {
 	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
 
 	// Per docs: date_on is optional (defaults to today), content is optional
-	_, err := lunatask.NewTimelineNote(personID).
-		Create(ctx(), client)
+	_, err := client.NewTimelineNote(personID).
+		Create(ctx())
 	if err != nil {
 		t.Fatalf("error = %v", err)
 	}
@@ -66,7 +66,7 @@ func TestCreateTimelineNote_Errors(t *testing.T) {
 	t.Parallel()
 
 	testErrorCases(t, func(c *lunatask.Client) error {
-		_, err := lunatask.NewTimelineNote(personID).Create(ctx(), c)
+		_, err := c.NewTimelineNote(personID).Create(ctx())
 
 		return err //nolint:wrapcheck // test helper
 	})