Skip to content

Add strategy pinning with manual ordering#77

Open
devin-ai-integration[bot] wants to merge 14 commits into
mainfrom
feature/strategy-pinning
Open

Add strategy pinning with manual ordering#77
devin-ai-integration[bot] wants to merge 14 commits into
mainfrom
feature/strategy-pinning

Conversation

@devin-ai-integration
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot commented May 31, 2026

Summary

Adds a manual ordering layer for pinned strategies/folders on top of PR #75’s pinning feature: pinned items are stored as zero-based order indexes, surfaced at the top of their current folder scope, and reordered by dragging one pinned tile/pill onto another pinned item.

Key details:

  • Legacy timestamp pins are read in recency order and migrated to manual indexes on first write.
  • Pin ordering is centralized via pinnedIdsInManualOrder() / _comparePinnedEntries() and applied through movePin(id, targetId, insertAfterTarget) from drag targets.
  • The old Move Pin to Top, Move Pin Up, and Move Pin Down context-menu actions were removed; menus now only expose Pin/Unpin plus the existing item actions.
  • Pinned folder/strategy lifting only uses items already present in the current folder view, so pins from subfolders do not duplicate into root.
  • Pin persistence writes the next order before deleting stale keys to avoid an empty-box window on interrupted saves.
  • Test-only preview paths now avoid Hive-backed preference/context-menu assumptions, and agent tap selection handles stale drag state from prior tests without disabling real taps.

Link to Devin session: https://app.devin.ai/sessions/026effa9989f4a9c9d8ef2da902bc575
Requested by: @SunkenInTime

@devin-ai-integration
Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 31, 2026

Greptile Summary

This PR adds strategy/folder pinning with manual ordering to the home screen. A new PinnedItemsProvider persists pin order in a Hive Box<int> (zero-based indices), with a backward-compatible comparator for legacy timestamp-based values written by the previous implementation.

  • PinnedItemsProvider exposes togglePin, removePin, movePinUp/Down, movePinToTop, and a drag-target movePin, all of which re-index and persist the full ordered list on each call.
  • FolderContent surfaces pinned items at the root view by looking up items across the entire Hive box and inserting them at the front of the rendered lists; FolderPill and StrategyTile gain Pin/Unpin and reorder context-menu actions.
  • deleteFolder and deleteStrategy now await removePin before deleting, keeping pin state consistent.

Confidence Score: 3/5

The core ordering and persistence logic is correct, but the root-view rendering mixes all-box lookups with root-filtered lists, causing pinned subfolder items to appear in two places at once.

The duplication in folder_content.dart is a real, user-facing behavioral inconsistency: a root-level pin moves cleanly to the top, but a subfolder pin creates a ghost copy at the home screen while the item also remains in its folder. This will be confusing and could lead to surprising interactions (e.g., a user thinking they moved an item to root when they only pinned it). The fix requires either restricting pins to root-level items or propagating pin-exclusion into subfolder views.

lib/widgets/folder_content.dart — the strategyById/folderById lookup and subsequent insertAll logic; lib/providers/pinned_items_provider.dart — the _saveOrder clear→putAll sequence.

Important Files Changed

Filename Overview
lib/providers/pinned_items_provider.dart New provider for pin ordering; logic is mostly sound but _saveOrder has a non-atomic clear→putAll window that can silently erase all pin data on crash.
lib/widgets/folder_content.dart Pinned items logic uses all-box lookups instead of root-filtered lists, causing subfolder-pinned items to appear both at root and inside their original subfolder simultaneously.
lib/widgets/folder_pill.dart Adds Pin/Unpin and reorder context-menu items with correct isDemo guards on all new actions.
lib/widgets/strategy_tile/strategy_tile.dart Adds matching Pin/Unpin and reorder menu items; StrategyTile has no isDemo concept so omitting the guard is correct.
lib/providers/folder_provider.dart Correctly awaits removePin before deleting the folder so the pin is cleaned up atomically with the folder deletion.
lib/providers/strategy_provider.dart Correctly awaits removePin before deleting the strategy, keeping pin state consistent on delete.
test/pinned_items_provider_test.dart Good test coverage for toggle, order, boundary moves, and legacy-timestamp migration path.

Sequence Diagram

sequenceDiagram
    participant UI as FolderPill or StrategyTile
    participant PIP as PinnedItemsProvider
    participant Hive as Hive Box int
    participant FC as FolderContent root

    UI->>PIP: togglePin(id)
    PIP->>PIP: pinnedIdsByManualOrder() returns [id, existing...]
    PIP->>Hive: box.clear()
    PIP->>Hive: box.putAll(id to 0, ...)
    PIP->>PIP: "state = nextState"

    UI->>PIP: movePinUp(id)
    PIP->>PIP: swap index in orderedIds
    PIP->>Hive: box.clear() + putAll reindexed
    PIP->>PIP: "state = nextState"

    FC->>Hive: watch strategiesListenable
    FC->>PIP: watch pinnedItemsProvider
    PIP-->>FC: Map String to int pinned
    FC->>FC: pinnedIdsInManualOrder(pinned)
    FC->>FC: build strategyById and folderById from ALL items
    FC->>FC: removeWhere pinned from root lists
    FC->>FC: insertAll(0, pinnedStrategies and pinnedFolders)
Loading

Reviews (1): Last reviewed commit: "Add manual pinned item ordering" | Re-trigger Greptile

Comment thread lib/widgets/folder_content.dart Outdated
Comment on lines +181 to +206
final strategyById = {
for (final s in strategyBox.values) s.id: s
};
final folderById = {
for (final f in folderBox.values) f.id: f
};

final pinnedStrategies = <StrategyData>[];
final pinnedFolders = <Folder>[];
for (final id in orderedPinnedIds) {
final s = strategyById[id];
if (s != null) {
pinnedStrategies.add(s);
continue;
}
final f = folderById[id];
if (f != null) pinnedFolders.add(f);
}

// Remove pinned items from their normal position, then
// re-insert at the front so they sort to the top.
strategies
.removeWhere((s) => pinned.containsKey(s.id));
folders.removeWhere((f) => pinned.containsKey(f.id));
strategies.insertAll(0, pinnedStrategies);
folders.insertAll(0, pinnedFolders);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Subfolder pins are duplicated at root

strategyById and folderById are built from the entire Hive box (strategyBox.values / folderBox.values), not from the already-filtered root-level lists. As a result, a strategy or folder that lives inside a subfolder and is pinned will be inserted into the root view via pinnedStrategies/pinnedFolders, while also continuing to appear normally inside its own subfolder (the subfolder's FolderContent has no pin-filtering logic). The removeWhere calls have no effect on these items because they were never in the root-filtered lists to begin with. Root-level pinned items are correctly "moved to top" (no duplicate), but subfolder-pinned items end up visible in two places simultaneously.

Comment on lines +100 to +107
Future<void> _saveOrder(List<String> orderedIds) async {
final nextState = <String, int>{
for (final entry in orderedIds.asMap().entries) entry.value: entry.key,
};
await _box.clear();
await _box.putAll(nextState);
state = nextState;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Non-atomic pin storage write

_box.clear() and _box.putAll() are two separate async operations. If the app is terminated (crash, force-close, OS kill) between the two calls, the box is left empty and all pin data is permanently lost. A safer pattern is to write the new state first and then remove stale keys, so there is never a window where valid data has been erased but the replacement has not yet landed.

Suggested change
Future<void> _saveOrder(List<String> orderedIds) async {
final nextState = <String, int>{
for (final entry in orderedIds.asMap().entries) entry.value: entry.key,
};
await _box.clear();
await _box.putAll(nextState);
state = nextState;
}
Future<void> _saveOrder(List<String> orderedIds) async {
final nextState = <String, int>{
for (final entry in orderedIds.asMap().entries) entry.value: entry.key,
};
// Write new entries first, then remove stale keys so there is no window
// where the box is empty if the app is terminated mid-write.
await _box.putAll(nextState);
final staleKeys =
_box.keys.whereType<String>().where((k) => !nextState.containsKey(k));
await _box.deleteAll(staleKeys.toList());
state = nextState;
}

@devin-ai-integration
Copy link
Copy Markdown
Author

Runtime tested PR #77 on Windows desktop (flutter run -d windows) against the manual pinned-strategy ordering flow.

Escalation: one unexpected Windows dialog appeared while quitting once: icarus.exe - System Error / Unknown Hard Error. Relaunch succeeded afterward and the pinned order persisted.

Manual pinned ordering test results
  • Passed: unpinned strategies sorted alpha pin test, middle pin test, zulu pin test under Alphabetical + Ascending.
  • Passed: pinning alpha, then middle, then zulu produced pinned stack zulu pin test, middle pin test, alpha pin test.
  • Passed: Move Pin to Top on alpha pin test produced alpha pin test, zulu pin test, middle pin test.
  • Passed: Move Pin Down on alpha pin test produced zulu pin test, alpha pin test, middle pin test.
  • Passed: switching normal sort to Alphabetical + Descending kept pinned stack zulu pin test, alpha pin test, middle pin test.
  • Passed: after relaunch, switching to Alphabetical + Descending still kept pinned stack zulu pin test, alpha pin test, middle pin test.
  • Failed: quitting once showed Unknown Hard Error before the app relaunched successfully.
Evidence
Before pinning Newest-first after pinning
Alphabetical order before pinning Newest-first after pinning
alpha, middle, zulu under normal sort zulu, middle, alpha after pinning
Manual order before sort change Manual order after descending sort
Move down manual order Descending still manual
zulu, alpha, middle after Move Pin Down zulu, alpha, middle despite descending alphabetical sort
Quit-time issue Post-relaunch persistence
Unknown Hard Error while quitting Relaunch descending manual order
Unknown Hard Error appeared once on quit Relaunch preserved zulu, alpha, middle

Recordings:

Devin session: https://app.devin.ai/sessions/026effa9989f4a9c9d8ef2da902bc575

@devin-ai-integration
Copy link
Copy Markdown
Author

Runtime tested the drag-reorder update on Windows desktop with fvm flutter run -d windows --no-pub.

Escalation: I used the existing pinned test strategies from the prior desktop validation instead of recreating fresh strategies in this pass. This specifically validates the requested replacement behavior: no context-menu reorder buttons and drag/drop manual order.

Drag-reorder test results
  • Passed: pinned strategy context menu shows Unpin, Rename, Duplicate, Export, Delete; no Move Pin to Top, Move Pin Up, or Move Pin Down actions.
  • Passed: dragging alpha pin test onto the left half of zulu pin test changed order to alpha pin test, zulu pin test, middle pin test.
  • Passed: Alphabetical + Descending did not override the manual pinned order.
  • Passed: quitting and relaunching the Windows desktop app preserved the dragged order.
  • Passed: dragging alpha pin test onto the right half of zulu pin test changed order to zulu pin test, alpha pin test, middle pin test.
  • Untested in this pass: creating fresh strategies and proving newest-pin insertion from scratch; this was covered in the earlier manual-order desktop run before the drag/drop replacement.
  • CI: PR Add strategy pinning with manual ordering #77 validate is passing.
Evidence
Context menu Left-half drop
Pinned menu without Move Pin actions Left-half drop order
Sort override After restart
Manual order preserved under descending sort Dragged order persisted after restart
Right-half drop
Right-half drop order

Annotated recording: https://app.devin.ai/attachments/5679b2de-1cae-4bd4-a6e4-7c3306ff8bd6/pinned-drag-reorder-windows-edited.mp4

Session: https://app.devin.ai/sessions/026effa9989f4a9c9d8ef2da902bc575

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