···22title: Bugs
33updated: 2026-03-18
44---
55-66-## Checklist
77-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)
1111-1212-## 1. Draft Save Redundancy — Cancel Prompts After Explicit Save
1313-1414-**Status:** Broken — user can save a draft, then immediately be asked to save again on
1515-cancel.
1616-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.
2222-2323-**Fix:**
2424-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.
3333-3434-**Files:**
3535-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`
4444-4545-## 2. Character Counter — No Initial State
4646-4747-**Status:** Incomplete — counter ring starts empty and invisible until the user types.
4848-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.
5454-5555-The progress ring itself also starts as just the background circle with no fill, giving
5656-no visual cue about what it represents.
5757-5858-**Fix:**
5959-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.
6464-6565-**Files:**
6666-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
7070-7171-## 3. Composer Layout — Drafts Should Be Inline, Not Full-Screen
7272-7373-**Status:** UX issue — drafts open as a modal bottom sheet that covers the composer.
7474-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.
8080-8181-The desired behavior is: drafts should appear inline, sharing the screen with the
8282-compose area, and be toggleable open/closed.
8383-8484-**Fix:**
8585-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.
9898-9999-**Files:**
100100-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)