feat(sections,projects): model description on read and write types#620
Conversation
Backend shipped project and section descriptions (26Q2 Project Essentials), but the SDK didn't model the section side and omitted `description` from the write args. This adds it so consumers (CLI, MCP) can read and write it without casts. Sections: - `SectionBaseSchema` gains `description: z.string().nullable()` (the column is nullable, null when unset), so REST and sync section reads carry it. - `WebhookSectionSchema` overrides `description` to tolerate omission, mirroring the existing `updatedAt` handling — leaner webhook payloads still parse. - `AddSectionArgs` gains optional `description`; `UpdateSectionArgs` makes `name` optional and adds `description?: string | null` (null clears, per the backend's NULL_CLEARS semantics), enabling description-only updates. Projects: - `AddProjectArgs` / `UpdateProjectArgs` gain optional `description` (empty string clears on update, per NULL_KEEPS_UNCHANGED). The project read schema already carried `description`, so no read change there. Tests cover read parsing (string + null), webhook tolerance, and that add/update forward `description` (and the section null-clear / project empty-string-clear contracts) in the request body. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@doistbot /review |
doistbot
left a comment
There was a problem hiding this comment.
This introduces the description field for both projects and sections across read and write types, properly accommodating the API's update asymmetry and webhook structures.
Few things worth tightening:
- Wrap
UpdateSectionArgsin aRequireAtLeastOneutility to prevent empty update requests now thatnameis optional. - Add a strictness test to ensure non-webhook section reads actively reject payloads missing a
description, preventing accidental schema loosening in the future.
Address review feedback on the description support:
- Wrap `UpdateSectionArgs` in a new `RequireAtLeastOne` type utility so that,
now that `name` is optional, an empty `{}` update no longer type-checks and
can't send an empty POST body.
- Add a strictness test asserting non-webhook section reads reject a payload
missing `description`, pinning the contract against accidental loosening (the
webhook variant keeps its tolerant override).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@doistbot /review |
doistbot
left a comment
There was a problem hiding this comment.
This adds solid support for reading and writing project and section descriptions while correctly navigating the backend's asymmetric clearing contracts.
Few things worth tightening:
- The
RequireAtLeastOneguard still allows effectively empty updates (like{ name: undefined }), which can be sealed by applying it to a required base shape or strippingundefined. - The webhook test covers the omitted-key branch but misses explicit
nulls; adding a test case for this will prevent accidental regressions if the schema ever changes from.nullish()to.optional().
I also included a few optional follow-up notes in the details below.
Optional follow-up note (1)
- [P3] src/types/sections/requests.ts:44:
RequireAtLeastOneis the key safety net here, but the suite doesn't pin it. The repo already uses@ts-expect-errorassertions for type contracts; add one forapi.updateSection('123', {})(or{}asUpdateSectionArgs) so the non-empty update guarantee can't silently regress.
Round-2 review follow-ups:
- `RequireAtLeastOne` now requires the chosen key to carry a *defined* value, so
`{ name: undefined }` (which serializes to an empty body) no longer satisfies
`UpdateSectionArgs`. `null` is preserved where the field allows it (the
`description` clear).
- Add a `@ts-expect-error` type-contract test pinning that `{}` and
`{ name: undefined }` are rejected while `{ name }` / `{ description: null }`
are accepted.
- Add a webhook test for an explicit `description: null` (complementing the
omitted-key case), so a future `.nullish()` -> `.optional()` change can't slip
through.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@doistbot /review |
doistbot
left a comment
There was a problem hiding this comment.
This cleanly models the new description field for projects and sections across the SDK's read and write types, effectively handling the backend's asymmetric update contracts and webhook edge cases. No issues were flagged.
doistbot
left a comment
There was a problem hiding this comment.
This PR cleanly models the new description field for projects and sections across the SDK's read and write interfaces.
Few things worth tightening:
- Pin the project clear semantics at the type level by adding a
@ts-expect-errortest for{ description: null }.
I also included a few optional follow-up notes in the details below.
Optional follow-up note (1)
- [P3] src/types/common.ts:17: This introduces a local
RequireAtLeastOne, but the repo already depends ontype-festand uses its request-shape helpers in sibling files liketypes/tasks/requests.tsandtypes/comments/requests.ts. Keeping a second in-house version of the same abstraction adds maintenance cost and makes the rules harder to remember. You can preserve the current “selected key must be defined” behavior by usingtype-fest’sRequireAtLeastOneon a required base shape instead (for example,RequireAtLeastOne<{ name: string; description: string | null }>inUpdateSectionArgs).
Address the remaining review thread: add a `@ts-expect-error` type-contract
test asserting `{ description: null }` does not type-check for projects. Projects
use NULL_KEEPS_UNCHANGED (clear with `""`), so `null` must not be accepted; this
locks the project/section clear asymmetry at the type level and would break if
`UpdateProjectArgs.description` were ever widened to `string | null`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@doistbot /review |
…Args
Address review follow-up: the repo already depends on type-fest and uses its
request-shape helpers in sibling files (tasks, comments), so drop the in-house
`RequireAtLeastOne` in favour of type-fest's. Applying it to a *required* base
shape (`{ name: string; description: string | null }`) preserves the same
"selected key must be defined" guarantee, so the existing `@ts-expect-error`
contract tests (rejecting `{}` and `{ name: undefined }`) still hold.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Simplify: the description-only and null-clear update tests shared near-identical MSW boilerplate. Combine them into a single set-and-clear test using the `capturedBodies` array pattern already used by the `folderId` test, asserting both the description set (with no `name` sent) and the null clear. Net -24 lines, same coverage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
## [10.4.0](v10.3.3...v10.4.0) (2026-06-11) ### Features * **sections,projects:** model description on read and write types ([#620](#620)) ([f319d96](f319d96))
|
🎉 This PR is included in version 10.4.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
…asts SDK 10.4.0 ships `description` on AddProjectArgs and UpdateProjectArgs (via Doist/todoist-sdk-typescript#620), so the temporary type workarounds in project create/update can go. Removed both casts; the SDK signature now covers the full payload. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lear SDK 10.4.0 (Doist/todoist-sdk-typescript#620) models section descriptions: `AddSectionArgs` / `UpdateSectionArgs` carry `description`, `UpdateSectionArgs` makes `name` optional (RequireAtLeastOne), and the section read schema exposes `description` — so section JSON output now surfaces it for real. - Drop the create-section description cast (now typed by the SDK). - Update-section: empty `--stdin` now clears via `null` (backend NULL_CLEARS), not an empty string. The remaining cast bridges RequireAtLeastOne, which a dynamically-built partial can't satisfy statically (the NO_CHANGES guard guarantees at least one field at runtime). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…asts SDK 10.4.0 (Doist/todoist-sdk-typescript#620) types `description` on AddProjectArgs / UpdateProjectArgs, so the temporary casts in add-projects and update-projects can go. The SDK's `Section` read type now requires `description`, so the shared section test fixtures (createMockSection helpers and a couple of inline Section literals) gain a `description: null` default to satisfy the stricter type. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ypes SDK 10.4.0 (Doist/todoist-sdk-typescript#620) models section descriptions: the `Section` read type carries `description`, and `UpdateSectionArgs` is now RequireAtLeastOne with `description: string | null`. - Drop the widened type on `toSectionSummary` (the SDK `Section` now has `description`); it maps the read's `string | null` to the output's optional string (Gemini-compatible). - update-sections keeps a single cast bridging RequireAtLeastOne (a dynamically built partial can't satisfy the union statically; at least one field is always present). `"remove"`/legacy `null` still clears via `null`. - Add `description: null` defaults to the shared section test fixtures the stricter `Section` type now requires. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
What
Models project and section
description(26Q2 Project Essentials) on the SDK's read and write types. The backend shipped both (sections landed 2026-06-10 via Todoist#28403), but the SDK didn't model the section side and omitteddescriptionfrom the write args — so the CLI and MCP currently need casts and can't read section descriptions at all. This unblocks them.Changes
Sections
SectionBaseSchemagainsdescription: z.string().nullable()— the backend column isstr | None(null when unset), so REST and sync section reads now carry it.WebhookSectionSchemaoverridesdescriptionto tolerate omission (.nullish()→null), mirroring the existingupdatedAthandling, so leaner webhook payloads still parse.AddSectionArgsgains optionaldescription;UpdateSectionArgsmakesnameoptional and addsdescription?: string | null(nullclears, matching the backend'sNULL_CLEARS), enabling description-only updates.Projects
AddProjectArgs/UpdateProjectArgsgain optionaldescription(empty string clears on update, matchingNULL_KEEPS_UNCHANGED). The project read schema already carrieddescription, so no read change there.Clear-semantics note (intentional asymmetry, from the backend)
descriptionto keep,""to clear.nullto clear.These mirror the backend's
NULL_KEEPS_UNCHANGED(projects) vsNULL_CLEARS(sections) contracts and are documented in the arg JSDoc.Testing
description(string andnull).null.addSection/updateSectionandaddProject/updateProjectforwarddescriptionin the request body, including the sectionnull-clear and project""-clear paths, plus a description-only section update (noname).ts-compile-checkerrors in apps/ui-extensions tests are unrelated to this change and present onmain.)Downstream
Unblocks the draft consumer PRs: CLI todoist-cli#395/#396 and MCP todoist-mcp#500/#501. Draft until reviewed; once released, bump the pins there to drop the temporary casts.
🤖 Generated with Claude Code