···11---
22-title: Bugs (Post Actions & Notifications — Fix Specification)
33-updated: 2026-03-17
22+title: Bugs
33+updated: 2026-03-18
44---
5566## Checklist
7788-- [x] [1. Post Thread Screen](#1-post-thread-screen)
99-- [x] [2. Post Tap Navigation](#2-post-tap-navigation)
1010-- [x] [3. Avatar Tap Navigation](#3-avatar-tap-navigation)
1111-- [x] [4. Quoted Post Tap Navigation](#4-quoted-post-tap-navigation)
1212-- [x] [5. Notification Tap Navigation](#5-notification-tap-navigation)
1313-- [x] [6. Viewer State on Own Posts](#6-viewer-state-on-own-posts)
1414-- [x] [7. Saved Posts Screen — Render Actual Posts](#7-saved-posts-screen--render-actual-posts)
1515-- [x] [8. Saved Posts — Accessible from Profile](#8-saved-posts--accessible-from-profile)
1616-- [x] [9. Saved Posts — Long Press for Local, Tap for Menu](#9-saved-posts--long-press-for-local-tap-for-menu)
1717-- [x] [10. Saved Posts — Show Save Counts](#10-saved-posts--show-save-counts)
1818-- [x] [11. Saved Posts — Cloud Save via AT Protocol](#11-saved-posts--cloud-save-via-at-protocol)
1919-- [x] [12. Failed Action Snackbar with Revert](#12-failed-action-snackbar-with-revert)
2020-- [x] [13. Delete Post — Remove from Feed](#13-delete-post--remove-from-feed)
2121-2222-## 1. Post Thread Screen
2323-2424-**Status:** Missing — no `/post` route or screen exists.
2525-2626-**Problem:** Multiple features navigate to `/post?uri=...` or `/post/{uri}`, but the
2727-route is not defined in `app_router.dart` and no screen exists. This breaks:
2828-2929-- Notification taps (like, repost, reply, mention, quote)
3030-- Saved post "open" action
3131-- Any future post deeplink
3232-3333-**Fix:**
3434-3535-- Create `lib/features/feed/presentation/post_thread_screen.dart`.
3636-- Use `designs/thread.html` as a reference for the UI.
3737-- Use the Bluesky `getPostThread` API to fetch the thread (parent chain + replies).
3838-- Display the focused post with full content plus its parent posts above and replies
3939- below, each rendered as `PostCardWithActions`.
4040-- Register route in `app_router.dart`:
4141- - Path: `/post` with query param `uri` (e.g. `/post?uri=at://...`).
4242-- Handle loading, error, and blocked/not-found thread states.
4343-4444-**Files:**
4545-4646-- New: `lib/features/feed/presentation/post_thread_screen.dart`
4747-- New: `lib/features/feed/data/post_thread_repository.dart` (wraps `getPostThread`)
4848-- Edit: `lib/core/router/app_router.dart` — add `/post` route
4949-5050-## 2. Post Tap Navigation
5151-5252-**Status:** Broken — tapping a post body does nothing.
5353-5454-**Problem:** `PostCard` is not wrapped in a tap detector. The only interactive elements
5555-are the action bar buttons and embedded media. Users expect tapping a post to open it.
5656-5757-**Fix:**
5858-5959-- Wrap the post content area (header + text + embed, excluding the action bar) in an
6060- `InkWell` that navigates to `/post?uri={postUri}`.
6161-- The action bar itself should NOT trigger navigation — only the content area above it.
6262-6363-**Files:**
6464-6565-- Edit: `lib/features/feed/presentation/widgets/post_card.dart` — wrap content in
6666- `InkWell` with navigation callback
6767-- Edit: `lib/features/feed/presentation/widgets/post_card_with_actions.dart` — pass
6868- `onTap` callback through to `PostCard`
6969-7070-## 3. Avatar Tap Navigation
7171-7272-**Status:** Broken — tapping a post author's avatar does nothing.
7373-7474-**Problem:** The avatar `CircleAvatar` in `PostCard._buildHeader` is not tappable.
7575-Users expect tapping an avatar to navigate to that user's profile.
7676-7777-**Fix:**
7878-7979-- Wrap the `CircleAvatar` in `_buildHeader` with a `GestureDetector` that navigates to
8080- `/profile/view?actor={author.did}`.
8181-- Requires passing the navigation callback or `BuildContext` with router access.
8282-8383-**Files:**
8484-8585-- Edit: `lib/features/feed/presentation/widgets/post_card.dart` — `_buildHeader`
8686- (line ~55)
8787-8888-## 4. Quoted Post Tap Navigation
8989-9090-**Status:** Incorrect — tapping a quoted post navigates to the quoted author's profile
9191-instead of the quoted post.
9292-9393-**Problem:** `PostCard._buildQuotedRecord` (line 342-346) navigates to
9494-`/profile/view?actor={quoted.author.did}`. It should open the quoted post in the thread
9595-screen.
9696-9797-**Fix:**
9898-9999-- Change the `onTap` in `_buildQuotedRecord` to navigate to
100100- `/post?uri={quoted.uri}` instead of the author's profile.
101101-102102-**Files:**
103103-104104-- Edit: `lib/features/feed/presentation/widgets/post_card.dart` — `_buildQuotedRecord`
105105- (line ~342)
106106-107107-## 5. Notification Tap Navigation
108108-109109-**Status:** Broken — tapping non-follow notifications crashes or does nothing (route
110110-doesn't exist).
111111-112112-**Problem:** `notification_list_item.dart:263` pushes `/post?uri=...` but the route is
113113-undefined. This is blocked by [#1](#1-post-thread-screen).
114114-115115-**Fix:**
116116-117117-- Once the `/post` route exists ([#1](#1-post-thread-screen)), notification taps will
118118- work. Verify the URI encoding is consistent (`Uri.encodeComponent` vs query param).
119119-- Current code: `context.push('/post?uri=${Uri.encodeComponent(uri.toString())}')`
120120-- Ensure the route handler decodes this correctly.
121121-- For like/repost notifications, `notification.uri` may point to the *liker's record*,
122122- not the original post. Verify that `reasonSubject` (the post that was liked) is used
123123- instead when appropriate.
124124-125125-**Files:**
126126-127127-- Edit: `lib/features/notifications/presentation/widgets/notification_list_item.dart`
128128- — `_onTap` (line ~256). May need to use `notification.reasonSubject` for like/repost
129129- notifications instead of `notification.uri`.
130130-131131-## 6. Viewer State on Own Posts
132132-133133-**Status:** Broken — current user's liked/reposted/saved posts don't show as active in
134134-the feed.
135135-136136-**Problem:** `PostCardWithActions` initializes `PostActionCubit` from `viewer.like` and
137137-`viewer.repost` (lines 35-36), which correctly reflects the API state. However:
138138-139139-- After the user likes a post, scrolls away, and scrolls back, the cubit is recreated
140140- from the stale `FeedViewPost` data (the original API response), losing the local
141141- optimistic state.
142142-- Saved state works correctly because `SavedPostsCubit` is global and checks the DB.
143143-144144-**Fix:**
145145-146146-- Maintain a lightweight in-memory cache (e.g. `Map<String, PostActionState>`) in a
147147- higher-level provider that `PostActionCubit` reads from on creation and writes to on
148148- state changes. This way, scrolling away and back preserves the user's actions within
149149- the session.
150150-- Alternatively, store the `likeUri`/`repostUri` in the feed bloc state so it survives
151151- cubit recreation.
152152-153153-**Files:**
154154-155155-- New or edit: A post action cache/provider (could be a simple `ChangeNotifier` or
156156- cubit at the feed level)
157157-- Edit: `lib/features/feed/presentation/widgets/post_card_with_actions.dart` — read
158158- from cache on cubit creation
159159-- Edit: `lib/features/feed/cubit/post_action_cubit.dart` — write to cache on state
160160- changes
161161-162162-## 7. Saved Posts Screen — Render Actual Posts
163163-164164-**Status:** Incomplete — saved posts screen shows metadata cards, not the actual post
165165-content.
166166-167167-**Problem:** `_SavedPostCard` in `saved_posts_screen.dart` shows a generic "Saved Post"
168168-`ListTile` with a date and action buttons. The full post JSON is stored in the DB
169169-(`postJson` column) but is never deserialized and rendered.
170170-171171-**Fix:**
172172-173173-- Deserialize `savedPost.postJson` back into a `PostView` and render it with
174174- `PostCardWithActions` (or a read-only variant).
175175-- The "open" button should navigate to `/post?uri={postUri}` (once [#1](#1-post-thread-screen) exists).
176176-- Keep swipe-to-dismiss for unsaving.
177177-178178-**Files:**
179179-180180-- Edit: `lib/features/feed/presentation/saved_posts_screen.dart` — replace
181181- `_SavedPostCard` with actual post rendering
182182-- The route in `_openPost` (line 186) currently uses path-style
183183- `/post/${Uri.encodeComponent(...)}` but should use query-style
184184- `/post?uri=${Uri.encodeComponent(...)}` to match the route definition.
185185-186186-## 8. Saved Posts — Accessible from Profile
187187-188188-**Status:** Incorrect location — saved posts are behind Settings, not on profiles.
189189-190190-**Problem:** The saved posts link is in `settings_screen.dart` (line 56-61). The
191191-requirement is that it should be accessible from profiles.
192192-193193-**Fix:**
194194-195195-- Add a "Saved Posts" button/tab on the current user's own profile screen.
196196-- Keep (or remove) the Settings entry as a secondary access point.
197197-198198-**Files:**
88+- [x] [1. Draft Save Redundancy — Cancel Prompts After Explicit Save](#1-draft-save-redundancy--cancel-prompts-after-explicit-save)
99+- [ ] [2. Character Counter — No Initial State](#2-character-counter--no-initial-state)
1010+- [ ] [3. Composer Layout — Drafts Should Be Inline, Not Full-Screen](#3-composer-layout--drafts-should-be-inline-not-full-screen)
19911200200-- Edit: profile screen (add saved posts navigation for the current user's profile)
201201-- Optionally edit: `lib/features/settings/presentation/settings_screen.dart`
1212+## 1. Draft Save Redundancy — Cancel Prompts After Explicit Save
20213203203-## 9. Saved Posts — Long Press for Local, Tap for Menu
1414+**Status:** Broken — user can save a draft, then immediately be asked to save again on
1515+cancel.
20416205205-**Status:** Missing — only tap-to-toggle exists, no long press or menu.
206206-207207-**Problem:** The bookmark button in `PostActionBar` only has `onTap` (line 82). The
208208-requirement is:
209209-210210-- **Long press** → save/unsave locally (instant, different icon color)
211211-- **Normal press** → show menu with options: save/remove locally, save/remove from
212212- cloud (ATProto)
213213-214214-Cloud save is not yet implemented, but the menu structure should be in place.
1717+**Problem:** The AppBar has both a "Save Draft" button (line 502) and a "Cancel" button
1818+(line 497). If the user taps "Save Draft" → draft is saved and a snackbar confirms it.
1919+If the user then taps "Cancel", `_handleBackNavigation` (line 426) checks `hasContent`
2020+(line 430), which is still `true` because the text/media haven't been cleared. The user
2121+is shown a "Save Draft?" dialog even though the draft was just saved moments ago.
2152221623**Fix:**
21724218218-- Add `onLongPress` to the bookmark `_ActionButton` in `PostActionBar`.
219219-- Long press: toggle local save immediately (current behavior), use a distinct color
220220- (e.g. amber/gold for local saves vs primary for cloud).
221221-- Normal press: show a bottom sheet with options:
222222- - "Save locally" / "Remove local save"
223223- - "Save to Bluesky" / "Remove from Bluesky" (disabled/placeholder until cloud is
224224- implemented)
225225-- Update `SavedPostsState` to distinguish local vs cloud saves.
2525+- Track whether the current content has been saved since the last edit. Add a
2626+ `isDraftDirty` (or `hasUnsavedChanges`) flag to `ComposeState`.
2727+- Set `isDraftDirty: true` when text or media changes (`_onTextChanged`,
2828+ `_onMediaChanged`, etc.).
2929+- Set `isDraftDirty: false` after a successful `DraftSaved` event.
3030+- In `_handleBackNavigation`, check `isDraftDirty` instead of (or in addition to)
3131+ `hasContent`. If content exists but `isDraftDirty` is `false`, skip the dialog and
3232+ pop immediately.
2263322734**Files:**
22835229229-- Edit: `lib/features/feed/presentation/widgets/post_action_bar.dart` — add
230230- `onLongPress`, show menu on tap
231231-- Edit: `lib/features/feed/cubit/saved_posts_cubit.dart` — support save type
232232- distinction
233233-- Edit: `lib/core/database/tables.dart` — add `saveType` column (local/cloud/both)
234234- with migration
235235-236236-## 10. Saved Posts — Show Save Counts
237237-238238-**Status:** Missing — hardcoded to `0` and hidden.
3636+- Edit: `lib/features/compose/bloc/compose_state.dart` — add `isDraftDirty` field
3737+ (default `true` for new compositions, `false` after draft load)
3838+- Edit: `lib/features/compose/bloc/compose_bloc.dart` — set `isDraftDirty: true` in
3939+ `_onTextChanged` (line 62) and media-change handlers; set `isDraftDirty: false` in
4040+ `_onDraftSaved` (line 214) and `_onDraftLoaded` (line 241)
4141+- Edit: `lib/features/compose/presentation/compose_screen.dart` —
4242+ `_handleBackNavigation` (line 426): gate the dialog on `state.isDraftDirty` rather
4343+ than just `hasContent`
23944240240-**Problem:** `PostActionBar` line 80: `count: 0` for the bookmark button. Save counts
241241-are never fetched or displayed.
4545+## 2. Character Counter — No Initial State
24246243243-**Fix:**
4747+**Status:** Incomplete — counter ring starts empty and invisible until the user types.
24448245245-- The Bluesky API provides `PostView.bookmarkCount` (nullable `int`). Pass this value
246246- through to `PostActionBar` instead of the hardcoded `0`.
247247-- Wire it up the same way `likeCount`/`repostCount` are: read from `post.bookmarkCount`
248248- in `PostCardWithActions` and pass to `PostActionBar`.
4949+**Problem:** `_CharCounter` (compose_screen.dart, line 895) only shows the remaining
5050+character count text when `count > 0` (line 918). On an empty compose screen the user
5151+sees a bare progress ring at 0% with no text — there is no indication of the 300-character
5252+limit. When loading a draft, the counter jumps from nothing to whatever the draft's count
5353+is, which feels jarring.
24954250250-**Files:**
251251-252252-- Edit: `lib/features/feed/presentation/widgets/post_card_with_actions.dart` — read
253253- `post.bookmarkCount ?? 0` and pass to action bar
254254-- Edit: `lib/features/feed/presentation/widgets/post_action_bar.dart` — use the passed
255255- count instead of hardcoded `0`
256256-257257-## 11. Saved Posts — Cloud Save via AT Protocol
258258-259259-**Status:** Not implemented — "Save to Bluesky" option is disabled with "Coming soon" placeholder.
260260-261261-**Problem:** The save menu in `PostActionBar._showSaveOptions()` has a disabled "Save to
262262-Bluesky" option. The `bluesky` package already exposes a bookmark API
263263-(`app.bsky.bookmark.*`) but it is not wired up. Currently all saves are local-only.
5555+The progress ring itself also starts as just the background circle with no fill, giving
5656+no visual cue about what it represents.
2645726558**Fix:**
26659267267-- Add bookmark methods to `PostActionRepository` using the existing `_bluesky.bookmark`
268268- service:
269269- - `createBookmark({uri, cid})` → `_bluesky.bookmark.createBookmark(uri, cid)`
270270- - `deleteBookmark({uri})` → `_bluesky.bookmark.deleteBookmark(uri)`
271271- - `getBookmarks({limit, cursor})` → `_bluesky.bookmark.getBookmarks(limit, cursor)`
272272-- Add `cloudSave` and `cloudUnsave` methods to `SavedPostsCubit`:
273273- - Call `PostActionRepository.createBookmark` / `deleteBookmark`.
274274- - On success, upsert the local DB row with `saveType: 'cloud'` (or `'both'` if already
275275- saved locally). On cloud unsave, downgrade `saveType` to `'local'` if a local save
276276- exists, or delete the row entirely.
277277- - Use optimistic UI: update the icon immediately, revert on failure.
278278-- Enable the "Save to Bluesky" / "Remove from Bluesky" option in
279279- `PostActionBar._showSaveOptions()` and wire it to `SavedPostsCubit.cloudSave` /
280280- `cloudUnsave` via a new callback.
281281-- Distinguish cloud vs local saves visually:
282282- - Local-only: amber/gold bookmark icon.
283283- - Cloud (or both): primary/blue bookmark icon.
284284- - `PostActionBar` already receives `isSaved`; extend it with a `saveType` parameter
285285- (or similar) so the icon color reflects the save type.
286286-- Add a one-time sync on login: call `getBookmarks` (paginated) and merge results into
287287- the local DB so cloud saves made on other clients appear. Mark these as `saveType:
288288- 'cloud'`.
6060+- Always show the remaining count text, even when `count == 0`. Remove the `if (count > 0)`
6161+ guard so the counter displays `300` on an empty compose screen.
6262+- This gives users an immediate signal: "you have 300 characters" — matching the behavior
6363+ of the official Bluesky app and Twitter/X composer.
2896429065**Files:**
29166292292-- Edit: `lib/features/feed/data/post_action_repository.dart` — add `createBookmark`,
293293- `deleteBookmark`, `getBookmarks` methods
294294-- Edit: `lib/features/feed/cubit/saved_posts_cubit.dart` — add `cloudSave`,
295295- `cloudUnsave`, `syncCloudBookmarks` methods; handle `saveType` transitions
296296-- Edit: `lib/features/feed/presentation/widgets/post_action_bar.dart` — enable cloud
297297- save option, accept `saveType` parameter, update icon color logic
298298-- Edit: `lib/features/feed/presentation/widgets/post_card_with_actions.dart` — pass
299299- `saveType` and cloud save/unsave callbacks to `PostActionBar`
6767+- Edit: `lib/features/compose/presentation/compose_screen.dart` — `_CharCounter.build`
6868+ (line 918): remove the `if (count > 0)` condition so the remaining count is always
6969+ visible
30070301301-## 12. Failed Action Snackbar with Revert
7171+## 3. Composer Layout — Drafts Should Be Inline, Not Full-Screen
30272303303-**Status:** Partially implemented — rollback works but snackbar is basic.
7373+**Status:** UX issue — drafts open as a modal bottom sheet that covers the composer.
30474305305-**Problem:** `PostActionCubit` correctly reverts optimistic updates on failure and shows
306306-a snackbar via `BlocListener` in `post_card_with_actions.dart` (lines 55-64). However:
7575+**Problem:** Tapping the drafts button (line 805) calls `_showDraftsDialog` (line 252),
7676+which opens a `showModalBottomSheet` with a `DraggableScrollableSheet` taking 60–90% of
7777+the screen. This obscures the compose area entirely, breaking the user's context. The
7878+overall composer is also described as "colossal" — the full-screen layout with the modal
7979+drafts on top makes it feel heavy.
30780308308-- The snackbar has no retry action button.
309309-- There's no visual indication during the loading state (the icon just sits there while
310310- `isLoadingLike`/`isLoadingRepost` is true).
8181+The desired behavior is: drafts should appear inline, sharing the screen with the
8282+compose area, and be toggleable open/closed.
3118331284**Fix:**
31385314314-- Add a "Retry" `SnackBarAction` to the error snackbar.
315315-- Show a subtle loading indicator on the action button while the network call is
316316- in-flight (e.g., replace the icon with a small spinner, or dim it). The
317317- `isLoadingLike`/`isLoadingRepost` fields already exist in state.
318318-319319-**Files:**
320320-321321-- Edit: `lib/features/feed/presentation/widgets/post_card_with_actions.dart` — add
322322- retry action to snackbar
323323-- Edit: `lib/features/feed/presentation/widgets/post_action_bar.dart` — show loading
324324- state visually on like/repost buttons
325325-326326-## 13. Delete Post — Remove from Feed
327327-328328-**Status:** Incomplete — post is deleted on the server but remains visible in the feed.
329329-330330-**Problem:** `PostActionCubit.deletePost()` (line 186-193) calls the API to delete but
331331-does not remove the post from the feed list. The deleted post card remains visible until
332332-the user refreshes.
333333-334334-**Fix:**
335335-336336-- After successful deletion, notify the parent feed bloc/cubit to remove the post from
337337- its list.
338338-- This could be done via a callback, a shared event bus, or by having the feed bloc
339339- listen for deletion events.
340340-- Show a confirmation snackbar: "Post deleted".
8686+- Replace the `showModalBottomSheet` drafts dialog with an inline, collapsible drafts
8787+ panel that sits below the compose text field (or above the bottom toolbar).
8888+- Use an `AnimatedContainer` or `ExpansionTile`-style widget that expands/collapses
8989+ when the drafts button is toggled.
9090+- When expanded, the drafts panel should take roughly half the available space, with the
9191+ compose text field shrinking to accommodate it. The text field remains visible and
9292+ editable above.
9393+- When collapsed, the panel is fully hidden and the compose area reclaims the space.
9494+- Add a toggle state (e.g. `_showDrafts` boolean in the screen's `State`) controlled by
9595+ the existing drafts `IconButton` (line 804).
9696+- Keep the same drafts list UI (ListTile with content preview, time, delete button, tap
9797+ to load) — just move it from a modal into the inline panel.
3419834299**Files:**
343100344344-- Edit: `lib/features/feed/cubit/post_action_cubit.dart` — emit a "deleted" state or
345345- invoke a callback
346346-- Edit: feed bloc/cubit — handle post removal from list
347347-- Edit: `lib/features/feed/presentation/widgets/post_card_with_actions.dart` — wire up
348348- deletion callback
101101+- Edit: `lib/features/compose/presentation/compose_screen.dart`:
102102+ - Add `_showDrafts` state variable to `_ComposeScreenState`
103103+ - Replace `_showDraftsDialog()` call on the drafts button (line 805) with a
104104+ `setState(() => _showDrafts = !_showDrafts)` toggle
105105+ - Add an inline drafts panel widget between the text field / media area and the
106106+ bottom toolbar (around line 767), wrapped in an `AnimatedSize` or similar for
107107+ smooth expand/collapse
108108+ - Remove or repurpose `_showDraftsDialog()` (lines 252-374) — extract the list
109109+ content into a reusable `_DraftsPanel` widget used by the inline panel
110110+ - Fire `DraftsRequested` event when the panel is opened (same as current behavior)
+5
docs/TODO.md
···6677## UX
8899+### Composer
1010+911- Odd behavior when saving drafts: can save draft via the button but hitting cancel
1012 prompts to save or discard.
1113- Character count doesn't have "initial state" (completely full)
1214- Composer is collosal -> We should show drafts on half the screen, with the option
1315 to toggle it closed.
1616+1717+### Dev Tools
1818+1419- Instead of tabs, navigating to a record through dev tools should instead show
1520 - A drawer on Tablet
1621 - Cards (stacked) on Mobile, with swipe to go back