Skip to content

Temporary strategies UI/UX#41

Draft
SunkenInTime wants to merge 1 commit intocursor/temporary-strategy-ux-19d9from
cursor/temporary-strategies-ui-ux-5b22
Draft

Temporary strategies UI/UX#41
SunkenInTime wants to merge 1 commit intocursor/temporary-strategy-ux-19d9from
cursor/temporary-strategies-ui-ux-5b22

Conversation

@SunkenInTime
Copy link
Owner

Redesign the UI/UX for temporary strategies (Draft Copy and Quick Board) to improve intuitiveness, clarity, and visual consistency.

The previous implementation felt unintuitive, with vague state indicators (e.g., "temporary strategy" replacing the name) and inconsistent design. This PR introduces clear visual cues (color-coded session bars, glowing dots, tinted borders), meaningful Lucide icons, and redesigned dialogs to make the temporary strategy state explicit and the user flow smoother. It also fixes a pre-existing bug where the Quick Board state was incorrectly overwritten after creation.

Open in Web Open in Cursor 

- Add amber (Draft Copy) and cyan (Quick Board) accent color tokens to Settings
- Create TemporarySessionBar widget: full-width colored bar between header and
  map showing session state, description, and contextual action buttons
  (Save to Original / Save as New / Save Board / Discard)
- Redesign StrategyQuickSwitcher: show original name with glowing colored dot
  indicator and tinted border in temp mode; replace 'Temporary Copy' button
  with clearer 'Draft Copy' button with pen icon and amber hover state;
  remove ambiguous 'Finish' button (actions now in session bar)
- Redesign temporary session dialogs with Lucide icons, color-coded action
  buttons, and clearer copy (e.g. 'Save Draft Changes?' with pen icon)
- Fix Quick Board double-load bug: navigateWithLoading was re-calling
  loadFromHive with default saved sessionKind, overwriting the quickBoard
  state set by createQuickBoard; added skipLoad parameter
- Update Quick Board button in folder navigator with cyan zap icon
- Remove unused strategy_provider import from strategy_save_icon_button

Co-authored-by: Dara Adedeji <SunkenInTime@users.noreply.github.com>
@cursor
Copy link

cursor bot commented Mar 10, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@vercel
Copy link

vercel bot commented Mar 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
icarus Ready Ready Preview, Comment Mar 10, 2026 10:16pm

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 11, 2026

Greptile Summary

This PR redesigns the temporary strategy UI/UX by introducing a new TemporarySessionBar widget, color-coded accent theming, updated dialogs, and a _DraftCopyButton component, while also fixing a pre-existing bug where Quick Board state was incorrectly overwritten on navigation.

Key changes:

  • TemporarySessionBar: A new animated banner rendered above the canvas that shows context-sensitive save/discard actions for Draft Copy and Quick Board sessions, replacing the old inline "Finish" button in StrategyQuickSwitcher.
  • folder_navigator.dart: Introduces skipLoad: true in navigateWithLoading to fix the Quick Board state-overwrite bug — the correct state is now preserved after creation.
  • strategy_quick_switcher.dart: Replaces the "Temporary Copy" label with a glowing dot indicator and accent-colored border; adds the new _DraftCopyButton with amber theming.
  • temporary_session_flow.dart: Dialog copy, icons, and button styles updated for clarity (e.g. "Save Draft Changes?", destructive Discard, accent-colored primary actions).
  • settings.dart: Eight new color constants added for Draft Copy (amber) and Quick Board (cyan) accent theming.

Issues found:

  • _exitDraft in TemporarySessionBar calls ref.read after an await without a mounted guard, which can throw a Riverpod StateError if the widget is disposed while the confirmation dialog is open.
  • The null assertion accentColor! in StrategyQuickSwitcher relies on an implicit invariant between isTemporarySession and _sessionAccentColor that is safe today but fragile against future StrategySessionKind additions.
  • Save action buttons in TemporarySessionBar lack a busy/disabled state, allowing duplicate concurrent saves on rapid clicks.

Confidence Score: 4/5

  • Safe to merge after addressing the missing mounted check in _exitDraft; remaining issues are minor style concerns.
  • The Quick Board state-overwrite bug fix is correct and well-scoped. The UI changes are well-structured and consistent. The primary concern is a missing mounted guard after an async gap in _exitDraft which can cause a Riverpod StateError in a real (if uncommon) scenario. The null assertion and lack of busy state are lower-severity style issues that don't affect the happy path.
  • lib/widgets/temporary_session_bar.dart — the _exitDraft async safety issue should be resolved before merging.

Important Files Changed

Filename Overview
lib/widgets/temporary_session_bar.dart New widget providing an animated banner for temporary sessions; has a missing mounted guard after an async gap in _exitDraft and no loading/busy protection on save buttons.
lib/widgets/strategy_quick_switcher.dart Replaces old "Finish" button with a new _DraftCopyButton; adds accent color cues and a glowing dot indicator. Contains a fragile null assertion (accentColor!) that is safe under the current enum but could crash if a new StrategySessionKind is added.
lib/widgets/dialogs/strategy/temporary_session_flow.dart Dialog copy and button styling improved (icons, accent colors, destructive Discard button); logic is unchanged and correct.
lib/widgets/folder_navigator.dart Introduces skipLoad parameter to navigateWithLoading to fix the pre-existing Quick Board bug where state was overwritten on navigation; clean and well-scoped fix.
lib/const/settings.dart Adds eight new color constants for Draft Copy and Quick Board accent theming; straightforward and well-organised.
lib/strategy_view.dart Inserts TemporarySessionBar into the layout column above the main canvas; minimal and correct change.
lib/widgets/strategy_save_icon_button.dart Removes now-unused strategy_provider.dart import; no functional changes.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User in StrategyView] --> B{isTemporarySession?}
    B -- No --> C[TemporarySessionBar hidden\nDraftCopyButton visible]
    B -- Yes --> D[TemporarySessionBar shown\nDraftCopyButton hidden]

    C -->|Click Draft Copy| E[startTemporaryCopyFromCurrentStrategy\nsessionKind = temporaryCopy]
    C -->|Click Quick Board| F[createQuickBoard\nsessionKind = quickBoard\nskipLoad = true]

    E --> D
    F --> D

    D -->|Draft Mode: Save to Original| G[overwriteOriginalFromTemporaryCopy]
    D -->|Draft Mode: Save as New| H[showStrategySaveDetailsDialog\n→ saveTemporarySessionAsNewStrategy]
    D -->|Quick Board: Save Board| H
    D -->|Click X / Discard| I[_showExitConfirmation dialog]

    I -->|Confirmed| J[discardTemporarySession\nsessionKind = saved]
    I -->|Cancelled| D

    G --> K[sessionKind = saved\nTemporarySessionBar animates out]
    H --> K
    J --> K
Loading

Last reviewed commit: 0401af2

Comment on lines +71 to +78
Future<void> _exitDraft() async {
final strategy = ref.read(strategyProvider);
final confirmed = await _showExitConfirmation(
isQuickBoard: strategy.isQuickBoard,
);
if (confirmed) {
await ref.read(strategyProvider.notifier).discardTemporarySession();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ref.read called after async gap without mounted check

ref.read(strategyProvider.notifier) is accessed after the await _showExitConfirmation(...) async gap with no mounted guard. In Riverpod's ConsumerStatefulWidget, accessing ref after the widget has been disposed throws a StateError. If the user dismisses the parent screen while the confirmation dialog is still open, ref will be dead when the dialog eventually resolves.

Suggested change
Future<void> _exitDraft() async {
final strategy = ref.read(strategyProvider);
final confirmed = await _showExitConfirmation(
isQuickBoard: strategy.isQuickBoard,
);
if (confirmed) {
await ref.read(strategyProvider.notifier).discardTemporarySession();
}
Future<void> _exitDraft() async {
final strategy = ref.read(strategyProvider);
final confirmed = await _showExitConfirmation(
isQuickBoard: strategy.isQuickBoard,
);
if (!mounted) return;
if (confirmed) {
await ref.read(strategyProvider.notifier).discardTemporarySession();
}
}

Comment on lines +392 to +403
decoration: BoxDecoration(
color: accentColor,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: accentColor!.withValues(alpha: 0.5),
blurRadius: 4,
),
],
),
),
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fragile null assertion on accentColor

accentColor is typed Color? and the null assertion operator ! is used on line 397 (accentColor!.withValues(alpha: 0.5)). This is safe today because StrategySessionKind only has quickBoard and temporaryCopy as non-saved values (covering all isTemporarySession cases), so _sessionAccentColor is always non-null here.

However, if a third StrategySessionKind were ever added, this would throw a Null check operator used on a null value crash at runtime. The type system cannot enforce the invariant. Consider using a local non-null binding or a fallback color instead:

if (currentStrategy.isTemporarySession) ...[
  const SizedBox(width: 10),
  Container(
    width: 7,
    height: 7,
    decoration: BoxDecoration(
      color: accentColor,
      shape: BoxShape.circle,
      boxShadow: [
        BoxShadow(
          color: (accentColor ?? Colors.white).withValues(alpha: 0.5),
          blurRadius: 4,
        ),
      ],
    ),
  ),
],

Comment on lines +225 to +249
if (strategy.isTemporaryCopy) ...[
_SessionBarButton(
onPressed: _saveToOriginal,
icon: LucideIcons.save,
label: 'Save to Original',
accentColor: accentColor,
isPrimary: true,
),
const SizedBox(width: 6),
_SessionBarButton(
onPressed: _saveAsNew,
icon: LucideIcons.filePlus,
label: 'Save as New',
accentColor: accentColor,
isPrimary: false,
),
] else ...[
_SessionBarButton(
onPressed: _saveAsNew,
icon: LucideIcons.save,
label: 'Save Board',
accentColor: accentColor,
isPrimary: true,
),
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Session bar action buttons lack a loading/disabled state

_SessionBarButton.onPressed is typed as a non-nullable VoidCallback, and the button always shows a click cursor. If a user rapidly clicks "Save to Original" or "Save Board" before the async operation completes, _saveToOriginal / _saveAsNew will be invoked concurrently, potentially triggering duplicate saves or writes to Hive.

Consider adding an isLoading / busy flag to _TemporarySessionBarState and passing a nullable onPressed to _SessionBarButton (similar to how _DraftCopyButton already handles its VoidCallback?) to disable the button while the operation is in flight.

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.

2 participants