* feat(models): add pinned and lastConnectedAt fields to Host Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(i18n): add translations for pinned and recently connected sections Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(vault): add pin toggle, lastConnectedAt tracking, and computed sections Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(vault): render Pinned and Recently Connected sections at root level Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(vault): add pin/unpin context menus and hover edit buttons in all views Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(vault): make breadcrumb a drop target for moving groups back to root Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(settings): add toggle for showing recently connected hosts section Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: resolve lint warnings for unused vars and unnecessary dependency Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: improve pin performance and add pop-in animation - Use ref for hosts in callbacks to avoid stale closures and unnecessary re-renders when hosts array changes - Add pop-in spring animation on pinned host cards with staggered delay for a satisfying visual effect Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: fix pop-in animation visibility and improve pin responsiveness - Move @keyframes pop-in out of @layer base to global scope so inline styles can reference it - Add translateY to animation for a bouncier, more satisfying feel - Use pinnedAnimKey to force card remount on pin changes so animation replays each time - Wrap onUpdateHosts in startTransition for non-blocking pin updates Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: only animate newly pinned card, increase section spacing - Track lastPinnedId instead of global animKey so only the newly pinned card gets the pop-in animation, not all existing pinned cards - Clear animation state via onAnimationEnd for clean re-trigger - Add mb-4 to Pinned and Recent sections for better visual separation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(vault): show pin indicator icon on pinned host cards Small semi-transparent pin icon in top-right corner of pinned host cards in the Hosts section (grid view only). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: use solid amber/yellow pin indicator icon Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: tilt pin indicator icon 45 degrees Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: replace pin indicator with filled amber star on all pinned cards Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: move lastConnectedAt tracking to App-level handleConnectToHost Previously updating lastConnectedAt in VaultView's handleHostConnect which could be lost during tab switches. Now tracked at the App level where all connections are handled, ensuring the timestamp persists regardless of UI navigation state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Codex review findings (P2 issues) 1. useStoredBoolean now syncs across same-window components via CustomEvent dispatch, so Settings toggle immediately updates VaultView 2. lastConnectedAt updated after connectToHost succeeds, not before 3. Pinned and Recently Connected sections now respect active search and tag filters Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address second round Codex review findings 1. Track lastConnectedAt on actual 'connected' status instead of session creation - handles via handleSessionStatusChange wrapper 2. Covers tray panel connections since all paths go through updateSessionStatus 3. Pinned/Recent cards now honor multi-select mode with checkbox UI instead of triggering connections Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address third round Codex review findings 1. [P1] Use hostsRef in handleSessionStatusChange to avoid overwriting concurrent host changes with stale snapshot 2. [P2] Exclude pinned/recent hosts from main host list at root level to prevent duplicate cards on screen 3. [P2] Remove Pin action from tree view context menu since tree view has no pinned ordering/indicator support Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address fourth round Codex review findings 1. [P1] Remove leftover onToggleHostPinned references in HostTreeView root-level component that were missed in previous cleanup 2. [P2] Add draggable + onDragStart to pinned/recent host cards so drag-and-drop between groups still works 3. [P3] Fix grouped view header count to exclude hosts already shown in pinned/recent sections Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use functional state update for lastConnectedAt, dedupe pinned from recent 1. [P2] Add updateHostLastConnected using setHosts(prev => ...) functional update pattern (same as updateHostDistro) to avoid overwriting concurrent host changes when multiple sessions connect simultaneously 2. [P3] Exclude pinned hosts from Recently Connected section to prevent duplicate cards between the two top sections Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: wire showRecentHosts into settings sync, clear pin on duplicate 1. [P2] Add showRecentHosts to SyncPayload settings so the preference survives cloud sync and settings export/import 2. [P2] Clear pinned and lastConnectedAt on duplicated hosts so copies don't inherit pin/recent status from the original Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
56 lines
2.2 KiB
TypeScript
56 lines
2.2 KiB
TypeScript
import { useCallback, useEffect, useState } from "react";
|
|
import { localStorageAdapter } from "../../infrastructure/persistence/localStorageAdapter";
|
|
|
|
/**
|
|
* Hook for persisting a boolean value to localStorage.
|
|
* Syncs across components in the same window via a custom event,
|
|
* and across windows via the native storage event.
|
|
* @param storageKey - The key to use for localStorage
|
|
* @param fallback - The default value if no stored value exists (defaults to false)
|
|
* @returns A tuple of [value, setValue] similar to useState
|
|
*/
|
|
export const useStoredBoolean = (
|
|
storageKey: string,
|
|
fallback: boolean = false,
|
|
) => {
|
|
const [value, setValue] = useState<boolean>(() => {
|
|
const stored = localStorageAdapter.readBoolean(storageKey);
|
|
return stored ?? fallback;
|
|
});
|
|
|
|
const setAndPersist = useCallback((next: boolean | ((prev: boolean) => boolean)) => {
|
|
setValue((prev) => {
|
|
const resolved = typeof next === "function" ? next(prev) : next;
|
|
localStorageAdapter.writeBoolean(storageKey, resolved);
|
|
// Notify other same-window consumers
|
|
window.dispatchEvent(
|
|
new CustomEvent("stored-boolean-change", { detail: { key: storageKey, value: resolved } }),
|
|
);
|
|
return resolved;
|
|
});
|
|
}, [storageKey]);
|
|
|
|
useEffect(() => {
|
|
// Sync from other components in the same window
|
|
const handleCustom = (e: Event) => {
|
|
const { key, value: newValue } = (e as CustomEvent).detail;
|
|
if (key === storageKey) setValue(newValue);
|
|
};
|
|
// Sync from other windows
|
|
const handleStorage = (e: StorageEvent) => {
|
|
if (e.key === storageKey) {
|
|
const stored = localStorageAdapter.readBoolean(storageKey);
|
|
setValue(stored ?? fallback);
|
|
}
|
|
};
|
|
window.addEventListener("stored-boolean-change", handleCustom);
|
|
window.addEventListener("storage", handleStorage);
|
|
return () => {
|
|
window.removeEventListener("stored-boolean-change", handleCustom);
|
|
window.removeEventListener("storage", handleStorage);
|
|
};
|
|
}, [storageKey, fallback]);
|
|
|
|
return [value, setAndPersist] as const;
|
|
};
|