Commit Graph

5840 Commits

Author SHA1 Message Date
Samuel Newman c726e3cf67 fix detection rebase change 2026-01-28 19:15:18 +02:00
Eric Bailey 83b47db1b6 Prevent runaway pagination 2026-01-28 19:15:18 +02:00
Samuel Newman cd7a39ecd7 remove extraneous comments 2026-01-28 19:15:18 +02:00
Samuel Newman a346f40d77 Add comment about headers 2026-01-28 19:15:18 +02:00
Samuel Newman c50074fb4f Enable pagination 2026-01-28 19:15:18 +02:00
Samuel Newman 834632ce1c Use minimal media mode for draft display - REVERT IF NEEDED 2026-01-28 19:15:18 +02:00
Samuel Newman 4a141e6363 fix type errors 2026-01-28 19:15:18 +02:00
Samuel Newman 9fcaa8ff39 refactor: pass full draft data instead of re-fetching
The useLoadDraft and useDeleteDraftMutation hooks were fetching drafts
via getDrafts() to look up a draft by ID. This was problematic because
getDrafts is paginated, so drafts not on the first page wouldn't be
found.

Changes:
- Add full Draft object to DraftSummary type
- useLoadDraft now takes Draft directly (only loads local media)
- useDeleteDraftMutation now takes {draftId, draft} to avoid re-fetch
- Update DraftItem and DraftsListDialog to pass full draft data

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:15:18 +02:00
Samuel Newman ca3fcfe89c simplify threadgate/postgate handling 2026-01-28 19:15:17 +02:00
Samuel Newman 92824b4b3a infinite query, rename file to queries 2026-01-28 19:15:17 +02:00
Samuel Newman 2f731146f8 ensure media unavaiable message is contrasty enough 2026-01-28 19:15:17 +02:00
Samuel Newman ad848c0000 Fix draft button behaviour when publishing, tweak buttons 2026-01-28 19:15:17 +02:00
Samuel Newman e5b700a3d2 fix: prevent double query string in GIF draft hydration
When loading a GIF from a draft, the URL was being corrupted with
double query strings like:
`?ww=498&hh=498?hh=498&ww=498`

This happened because:
1. serializeGif() adds ?ww=X&hh=Y&alt=Z to the Tenor URL
2. parseGifFromUrl() returned the full URL including our params
3. resolveGif() in resolve.ts then appends MORE params via string
   concatenation, creating a second ?

Fix: Strip our custom params (ww, hh, alt) from the URL in
parseGifFromUrl() before returning it, so the reconstructed GIF
has a clean base URL.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:15:17 +02:00
Samuel Newman f396bac685 fix: handle invalid GIF dimensions gracefully
Fix NaN aspectRatio when rehydrating GIFs from drafts by:

1. Adding validation in parseTenorGif to reject invalid dimensions
   (NaN, zero, or negative values)
2. Adding defensive checks in GifEmbed to fallback to 1:1 aspect
   ratio if dimensions are invalid
3. Adding defensive checks in composer ExternalEmbedGif to fallback
   to 16:9 if gif.media_formats.gif.dims is missing or invalid

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:15:17 +02:00
Samuel Newman 5a67dc9110 move state to colo with composer 2026-01-28 19:15:17 +02:00
Samuel Newman c6539dd033 delete left-over file 2026-01-28 19:15:17 +02:00
Samuel Newman 451d7f6b1b log unknown errors 2026-01-28 19:15:17 +02:00
Samuel Newman 1d6123888b make storage functions async to match web 2026-01-28 19:15:17 +02:00
Samuel Newman c374441cca refactor: use typed error check for draft limit
Replace manual error object inspection with the proper
AppBskyDraftCreateDraft.DraftLimitReachedError type check for
cleaner and more reliable error handling.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:15:17 +02:00
Samuel Newman f21757f264 style: add bottom padding to drafts list
Add pb_xl padding to the drafts list content container for better
visual spacing at the bottom of the list.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:15:17 +02:00
Samuel Newman 095ce2865d fix: update save prompt copy when editing existing draft
When editing an existing draft (vs creating a new one), use "Save
changes" instead of "Save draft" in the save/discard prompts. This
provides clearer context to the user about what action they're taking.

Add isEditingDraft prop to DraftsButton and ComposerTopBar, and
update both prompts (in DraftsButton and Composer) with conditional
copy based on whether we're editing an existing draft.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:15:17 +02:00
Samuel Newman 7c129c0aec fix: load draft preview images and get dimensions
Fix draft preview images not showing on web and add proper aspect
ratio support:

1. Try to load all images regardless of the exists cache flag, which
   may be stale due to async cache population timing
2. Use Image.loadAsync() from expo-image to get image dimensions
3. Pass dimensions to viewImages for proper aspect ratio in previews

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:15:17 +02:00
Samuel Newman 0992c9376e fix: complete Gif object reconstruction for draft rehydration
Fix "Cannot read property 'url' of undefined" error when rehydrating
drafts with GIFs. The Gif object was missing required properties like
url, content_description, and media_formats.preview that are needed
by useResolveGifQuery and other components.

Also preserve alt text through serialization by storing it in URL
query params alongside dimensions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:15:17 +02:00
Samuel Newman 28da42fd61 fix: ensure media cache is populated before checking exists
On iOS (and web), the media cache wasn't populated before the drafts
query ran, causing drafts with local media to incorrectly show as
"missing media" on app restart. The issue would resolve itself after
closing and reopening the composer because by then the cache was ready.

This fix adds ensureMediaCachePopulated() and awaits it in useDrafts
before checking which media exists locally.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:15:17 +02:00
Samuel Newman a0ed43d903 refactor: use drafts-specific logger in hooks and storage
Switch from the generic logger to the new drafts-specific logger
for better log categorization.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:15:17 +02:00
Samuel Newman 12763bee76 feat: add drafts-specific logger
Add a Drafts context to the logger system for better log categorization
and debugging of draft-related operations.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:15:17 +02:00
Samuel Newman 9c8533c05e Migrate storage.ts from expo-file-system/legacy to expo-file-system
Use the new object-based expo-file-system API (SDK 54+) with Directory
and File classes instead of the legacy function-based API. The new API
provides synchronous operations for file/directory existence checks,
creation, copying, deletion, and listing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:15:17 +02:00
Samuel Newman 476a01a78f attempt to fix blob mangement 2026-01-28 19:15:17 +02:00
Samuel Newman 666e1318b2 Migrate drafts from local storage to server API
Replace local-only draft storage with the new `app.bsky.draft.*` server API:
- getDrafts, createDraft, updateDraft, deleteDraft endpoints

Key changes:
- Add api.ts with type converters (ComposerState <-> server Draft)
- Update hooks.ts to use server API instead of local storage
- Simplify storage.ts/storage.web.ts for local media caching only
- Media stored locally via localRef pattern (filepath in server draft)
- GIFs stored as external embeds with Tenor URL + dimensions
- Hide drafts button when replying (reply drafts not supported)
- Show "different device" note when media is missing locally

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:15:17 +02:00
Samuel Newman 2dbee371f3 fix import 2026-01-28 19:15:17 +02:00
Samuel Newman 31cd18f6e1 fix import 2026-01-28 19:14:42 +02:00
Samuel Newman d89fb09e6c add vertical option to prompt, change copy 2026-01-28 19:14:42 +02:00
Samuel Newman 813d972fdf Fix re-saving drafts with existing media
When re-saving a draft, the code was trying to copy media files that
were already in drafts storage to new locations, causing copy errors.

Changes:
- Add extractLocalIdFromPath() to detect if a path is already in drafts
- Track loadedMediaMap in ComposerState for identifying reusable media
- Only delete old media that wasn't reused during re-save
- Pass loadedMediaMap when saving to enable media reuse detection
- Disable pointer events on draft media preview

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:14:42 +02:00
Samuel Newman ec554210dc Apply dirty tracking to Drafts button prompt
Only show the save/discard prompt when pressing the Drafts button if
the composer has unsaved changes (isDirty). If the content is unchanged
from a loaded draft or was just saved, go directly to the drafts list.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:14:42 +02:00
Samuel Newman 4cccaf5d8e Mark composer as clean after saving draft
Add mark_saved action that resets isDirty to false and updates the
draftId. This is dispatched after successfully saving a draft, allowing
the user to close the composer without a discard prompt since their
changes have been saved.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:14:42 +02:00
Samuel Newman 794891db1e Improve drafts dialog platform handling
- Render header outside FlatList on native, inside on web
- Use web() helper for conditional web-only props
- Replace ItemSeparatorComponent with mt_lg margin on items
- Add minHeight on web for better dialog sizing
- Simplify header structure

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:14:42 +02:00
Samuel Newman 9a681a22ef Use real image embed components for draft preview
Replace custom image preview with AutoSizedImage for single images
and ImageLayoutGrid for multiple images. This gives drafts the same
polished image display as regular posts.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:14:42 +02:00
Samuel Newman 56bd02e7b3 Fix draft item styling based on feedback
- Add border and shadow to draft cards
- Remove trash button, move delete to overflow menu prompt
- Remove size differences for thread posts (same avatar/text size)
- Add spacing between header and first draft item
- Change prompt wording to "Discard draft"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:14:42 +02:00
Samuel Newman b7563a4127 Redesign drafts list to show full thread preview
- Display all posts in a draft thread, not just the first
- First post uses larger avatar (42px), subsequent posts nested with
  smaller avatar (32px) and thread connector line
- Show author avatar, display name, handle, and relative timestamp
- Add overflow menu button (placeholder) on first post
- Display full text instead of truncated preview
- Add media preview component for images, GIFs, and videos
- Card layout with rounded corners and proper spacing
- Add gap separators between draft cards in list

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:14:42 +02:00
Samuel Newman 092528dc05 Track dirty state to skip discard prompt for unchanged drafts
When a draft is loaded and the user hasn't made any changes, closing
the composer should not show the discard prompt since nothing would
be lost.

- Add isDirty field to ComposerState
- Set isDirty: true on all content-modifying actions
- Set isDirty: false on restore_from_draft, clear, and initial state
- Update onPressCancel to only show prompt if no draft or isDirty

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:14:42 +02:00
Samuel Newman 10d80dab7b Add clear action to discard composer content
When pressing the Drafts button with content in the composer, the user
can choose to discard. This now properly clears the composer by
dispatching a 'clear' action that resets to an empty state.

- Add 'clear' action type to ComposerAction
- Implement clear case in composerReducer (resets to single empty post)
- Add handleClearComposer callback in Composer.tsx
- Pass onDiscard prop through ComposerTopBar to DraftsButton
- Call onDiscard before opening drafts dialog on discard

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:14:42 +02:00
Samuel Newman 08e83b6259 Track draft ID in composer state machine
Adds draftId to ComposerState so that editing an existing draft and
saving it again updates the draft rather than creating a new one.

- Add draftId?: string to ComposerState type
- Set draftId when restoring from draft via restore_from_draft action
- Pass existingDraftId to save functions from composerState.draftId
- Add PageX icon for empty drafts state

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:14:42 +02:00
Samuel Newman ead99f123b Use InnerFlatList and Dialog.Header for drafts dialog
- Switch from ScrollableInner to InnerFlatList
- Add Dialog.Header with back button in left slot
- Use sticky header

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:14:42 +02:00
Samuel Newman ff3be5f841 Increase drafts button horizontal padding and gap
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:14:42 +02:00
Samuel Newman 0f91bb0067 Remove draft count from button, just show "Drafts"
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:14:42 +02:00
Samuel Newman ec6b1cb214 Update drafts button: text-only, ghost/primary style
- Show "Drafts" or "Drafts (N)" as text, no icon
- Use ghost variant with primary color
- Match Cancel button styling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:14:42 +02:00
Samuel Newman 4eb064b6a4 Fix drafts button: always visible, adjacent to post button
- Make drafts button always visible (not just when drafts exist)
- Move button to be adjacent to the publish button
- If composer is empty: opens drafts list directly
- If composer has content: shows prompt to save/discard before viewing drafts
- Add badge showing draft count when drafts exist

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:14:42 +02:00
Samuel Newman 4a2ee05660 Add drafts functionality to composer
- Add local storage layer for drafts (filesystem on native, IndexedDB on web)
- Add "Drafts" button to composer top bar showing badge with draft count
- Modify discard prompt to offer "Save Draft" option
- Add `restore_from_draft` action to composer reducer
- Support saving/restoring: text, facets, images, labels, threadgate, quote/link embeds
- Add placeholder hooks for future server API integration
- Add unit tests for draft serialization

Note: Video/GIF restoration marked as TODO for future implementation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:14:42 +02:00
Samuel Newman 277e985e1f prevent lightbox initial jitter (#9762) 2026-01-28 07:46:53 -08:00
Samuel Newman 3b2573bc56 [Lighbox perf - 1] Fix memoization of List component, defer re-rendering when opening lightbox (#9759)
* memoize FlatList_INTERNAL

* defer render, put handleScrolledDownChange in non reactive callback

* lintfix useDedupe
2026-01-28 07:35:43 -08:00