Skip to content

Enable exactOptionalPropertyTypes in shared and webapp - #930

Merged
hanzei merged 2 commits into
masterfrom
claude/github-issue-927-u95bvi
Aug 17, 2026
Merged

Enable exactOptionalPropertyTypes in shared and webapp#930
hanzei merged 2 commits into
masterfrom
claude/github-issue-927-u95bvi

Conversation

@hanzei

@hanzei hanzei commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Closes #927.

Turns on exactOptionalPropertyTypes in shared/tsconfig.json and webapp/tsconfig.json (which also covers tsconfig.node.json and tsconfig.e2e.json via extends, plus playwright.config.ts). Mobile is deliberately left for a follow-up issue, per #927's own suggested shape — it compiles shared/src directly, so it gets the 3 shared fixes for free, but its own ~54 errors are a separate pass once the widen-vs-spread convention is settled here.

What changed

shared/ (3 errors → 0):

  • collaborators.ts's buildCollaborators — was assigning possibly-undefined values (owner?.first_name, etc.) directly onto Collaborator's optional fields. Switched to conditional spread so the key is genuinely absent rather than present-with-undefined.
  • inlineMarkdown.ts's codespan case — narrowed the computed src into a local before using it in the object literal, matching the pattern the sibling strong/em/del cases already used.

webapp/ (44 errors after shared → 0):

  • The TFunction cluster (8 errors), all in NoteModal.tsx: a hand-rolled type TFunction = (key: string, opts?: Record<string, unknown>) => string didn't structurally match i18next's own (much more complex, overloaded) TFunction type once the flag was on — an overload-matching quirk, not a real optional-property bug. Replaced it with import type { TFunction } from 'i18next'.
  • Presentational / pass-through props (the bulk)LetterAvatar, NavigationHeader/ProfileMenu, Sidebar, MarkdownToolbar, SortableItem, NoteCard/SortableNoteCard/AnimatedNoteGrid, and the useNoteDraft/useNoteImages/useCompletedItems/LabelPicker hook-options objects only ever read their optional fields via ?./??/truthiness — nothing spreads them onward. These are widened to T | undefined explicitly, which changes no behavior.
  • NoteModal's note prop — instead of widening every hook's note option, note is now defaulted to null at the top of the component ({ note = null, ... }), so the rest of the component (and the hooks it feeds) keep treating it as Note | null like they already did everywhere via ?..
  • Construction sites building a fresh object from possibly-undefined sources (shareAvatars.ts, Toast.tsx's new-toast object) use conditional spread, same as collaborators.ts.
  • Merge sites that reset a field to undefineduseNoteImages's retry handler and Dashboard's SSE image-patch handlers were doing {...prev, field: undefined}, which is exactly the silent-field-wipe bug class the flag exists to catch. Rewritten to omit the key (or return the object unchanged) instead of setting it to undefined.
  • CreateNoteRequest/CreateListNoteRequest.labels (shared/src/types.ts) — widened rather than spread-fixed, since these are POST request bodies serialized with JSON.stringify, which drops undefined-valued keys before the distinction ever reaches the server.
  • playwright.config.ts's workers: process.env.CI ? 4 : undefined → conditional spread, since Playwright's own config type doesn't accept an explicit undefined there.

Verification

  • task check passes (lint, all tests, docs/migrations/translations checks).
  • shared, webapp/tsconfig.json, tsconfig.node.json, and tsconfig.e2e.json all type-check clean with the flag on.
  • No behavior changes — this is a type-level tightening plus the two genuine bug-class fixes called out above (the SSE image-patch and image-retry merge sites).

Not in scope

No screenshots — this is a type-checking change with no UI or behavior difference; the two fixed merge-site bugs (stale error message surviving an image-upload retry, and a possible spurious state update on an SSE image event when there were no prior images) aren't independently visible in a screenshot.


Generated by Claude Code

claude added 2 commits August 16, 2026 21:00
Closes #927 (shared + webapp portion; mobile is deliberately deferred
to a follow-up, per the issue).

shared/: fixes buildCollaborators (conditional spread instead of
assigning possibly-undefined fields) and the codespan case in
normalizeInlineTokens (narrow the local `src` before using it, since
the object literal's src property can't be assigned an explicitly
optional-undefined value under the new flag).

webapp/: fixes fall into a few groups:
- Presentational components (LetterAvatar, NavigationHeader, Sidebar,
  MarkdownToolbar, SortableItem, NoteCard/SortableNoteCard/
  AnimatedNoteGrid, hook option objects) only ever read their optional
  props via `?.`/`??`/truthiness, so their prop types are widened to
  `T | undefined` explicitly.
- Construction sites that build a fresh object from possibly-undefined
  source values (shareAvatars.ts, Toast.tsx) use conditional spread so
  the key is genuinely absent rather than present-with-undefined.
- Merge sites that reset a field via `{...prev, field: undefined}`
  (useNoteImages retry, Dashboard's SSE image-patch handlers) are
  rewritten to omit the key instead, which is the actual bug class the
  flag catches.
- CreateNoteRequest/CreateListNoteRequest's `labels` field is widened
  in shared/types.ts since it's serialized via JSON.stringify before
  reaching the server, which drops undefined-valued keys anyway.
- NoteModal.tsx's hand-rolled TFunction type is replaced with i18next's
  own TFunction import, fixing 8 errors that were an overload-matching
  quirk rather than a real optional-property issue.
- playwright.config.ts's `workers: undefined` is replaced with a
  conditional spread.

No behavior changes; verified with `task check`.
@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5281c92c-de8d-4ec9-99dd-0afe4f4b68d2

📥 Commits

Reviewing files that changed from the base of the PR and between f4b626f and 9d57435.

📒 Files selected for processing (26)
  • shared/src/collaborators.ts
  • shared/src/inlineMarkdown.ts
  • shared/src/types.ts
  • shared/tsconfig.json
  • webapp/playwright.config.ts
  • webapp/src/components/AnimatedNoteGrid.tsx
  • webapp/src/components/LabelPicker.tsx
  • webapp/src/components/LetterAvatar.tsx
  • webapp/src/components/MarkdownToolbar.tsx
  • webapp/src/components/NavigationHeader.tsx
  • webapp/src/components/NoteCard.tsx
  • webapp/src/components/NoteModal.tsx
  • webapp/src/components/ShareModal.tsx
  • webapp/src/components/Sidebar.tsx
  • webapp/src/components/SortableItem.tsx
  • webapp/src/components/SortableNoteCard.tsx
  • webapp/src/components/Toast.tsx
  • webapp/src/hooks/__tests__/useCompletedItems.test.ts
  • webapp/src/hooks/__tests__/useNoteDraft.test.ts
  • webapp/src/hooks/__tests__/useNoteImages.test.ts
  • webapp/src/hooks/useCompletedItems.ts
  • webapp/src/hooks/useNoteDraft.ts
  • webapp/src/hooks/useNoteImages.ts
  • webapp/src/pages/Dashboard.tsx
  • webapp/src/utils/shareAvatars.ts
  • webapp/tsconfig.json

Included review availability: Your plan includes up to 1 review per rolling hour; 0 remain after this review.


Walkthrough

The shared and webapp TypeScript configurations enable exactOptionalPropertyTypes. Shared request and collaborator construction now distinguishes omitted fields from explicit undefined. Webapp component and hook declarations explicitly permit undefined optional values. Avatar, toast, upload retry, and dashboard image-update logic now omit or reset optional fields. Test helpers normalize omitted notes to null. The Markdown codespan normalization keeps equivalent rendering behavior.

Possibly related PRs

  • hanzei/jot#784: Overlaps with the updated NoteModal, sortable card, and note hook code.
  • hanzei/jot#86: Introduced avatar and sharing code updated by this change.
  • hanzei/jot#825: Overlaps with the inlineMarkdown.ts normalization logic.

Poem

I’m a rabbit, hopping through types,
Removing undefined from little data stripes.
Avatars grow tidy, notes stay bright,
Uploads restart in a clean state tonight.
Exact fields now thump just right! 🐇

Merge Risk: ⚪ Minimal · up to 9d574

This change tightens optional-property type checking and updates affected shared and webapp code without introducing a known behavior change; the reported checks pass, so no actionable merge-blocking risk remains beyond normal review.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% 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
Title check ✅ Passed The title clearly and concisely identifies the primary change: enabling exactOptionalPropertyTypes in shared and webapp.
Description check ✅ Passed The description directly explains the flag enablement, resulting fixes, verification, and the deliberate mobile follow-up scope.
Linked Issues check ✅ Passed The changes satisfy issue #927 by enabling the flag and resolving the reported shared and webapp type errors while leaving mobile deferred.
Out of Scope Changes check ✅ Passed The changes are limited to enabling exactOptionalPropertyTypes and fixing related shared, webapp, request-body, and Playwright typing issues.

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.

@hanzei
hanzei marked this pull request as ready for review August 17, 2026 06:18
@hanzei
hanzei merged commit 0c3aecf into master Aug 17, 2026
18 checks passed
@hanzei
hanzei deleted the claude/github-issue-927-u95bvi branch August 17, 2026 06:25
hanzei added a commit that referenced this pull request Aug 17, 2026
* Enable exactOptionalPropertyTypes in mobile

Completes the workspace rollout #927 deliberately deferred to a
follow-up: shared and webapp landed in #930, mobile compiles the same
shared/src and gets those three fixes for free, then adds its own ~49
sites. Each resolves the same way #930 established — widen a
pass-through prop or a JSON request/query field to `T | undefined`
(the value never reaches the server either way), or conditionally
spread at a construction/merge site where the key's presence is
meaningful (the retry-reset image-upload path, the toast queue).

Also updates CLAUDE.md: the "nothing anywhere runs
exactOptionalPropertyTypes" gap note is now stale, replaced with the
widen-vs-conditional-spread convention itself.

* Drop exactOptionalPropertyTypes convention writeup from CLAUDE.md

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

Turn on exactOptionalPropertyTypes — shared first, then webapp

2 participants