Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions frontend/src/lib/pages/dashboard-personal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
import * as Card from "$lib/components/ui/card";
import type { Task, Project } from "$lib/types/api";
import { ACTIVE_STATUSES } from "$lib/types/api";
import { getSnapshot, saveSnapshot } from "$lib/stores/task-filters.svelte";

let { params = {} }: { params?: Record<string, string> } = $props();

const ROUTE_KEY = "/dashboard";

let createdTasks = $state<Task[]>([]);
let assignedTasks = $state<Task[]>([]);
let projects = $state<Project[]>([]);
Expand All @@ -25,6 +28,14 @@
let searchQuery = $state("");
let sort = $state("-created_at");

let saved = getSnapshot(ROUTE_KEY);
if (saved) {
filterProject = saved.filterProject ?? "";
filterStatuses = saved.filterStatuses;
filterPriorities = saved.filterPriorities;
sort = saved.sort;
}

function toggleStatus(s: string) {
if (filterStatuses.includes(s)) {
filterStatuses = filterStatuses.filter((x) => x !== s);
Expand Down Expand Up @@ -60,6 +71,16 @@
navigate(`/tasks/${task.id}`);
}

$effect(() => {
saveSnapshot(ROUTE_KEY, {
filterProject: filterProject || undefined,
filterStatuses: [...filterStatuses],
filterPriorities: [...filterPriorities],
sort,
offset: 0,
});
});

$effect(() => {
loading = true;
error = "";
Expand Down
22 changes: 22 additions & 0 deletions frontend/src/lib/pages/dashboard-tasks.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@
import { Button } from "$lib/components/ui/button";
import type { Project, Task } from "$lib/types/api";
import { ACTIVE_STATUSES } from "$lib/types/api";
import { getSnapshot, saveSnapshot } from "$lib/stores/task-filters.svelte";

let { params = {} }: { params?: Record<string, string> } = $props();

const ROUTE_KEY = "/dashboard/all";

let tasks = $state<Task[]>([]);
let total = $state(0);
let projects = $state<Project[]>([]);
Expand All @@ -28,6 +31,15 @@
let offset = $state(0);
const limit = 50;

let saved = getSnapshot(ROUTE_KEY);
if (saved) {
filterProject = saved.filterProject ?? "";
filterStatuses = saved.filterStatuses;
filterPriorities = saved.filterPriorities;
sort = saved.sort;
offset = saved.offset;
}

function toggleStatus(s: string) {
if (filterStatuses.includes(s)) {
filterStatuses = filterStatuses.filter((x) => x !== s);
Expand Down Expand Up @@ -98,6 +110,16 @@

$effect(load);

$effect(() => {
saveSnapshot(ROUTE_KEY, {
filterProject: filterProject || undefined,
filterStatuses: [...filterStatuses],
filterPriorities: [...filterPriorities],
sort,
offset,
});
});

function handleTaskClick(task: Task) {
navigate(`/tasks/${task.id}`);
}
Expand Down
19 changes: 19 additions & 0 deletions frontend/src/lib/pages/project-tasks.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
import { Button } from "$lib/components/ui/button";
import type { Project, Task } from "$lib/types/api";
import { ACTIVE_STATUSES } from "$lib/types/api";
import { getSnapshot, saveSnapshot } from "$lib/stores/task-filters.svelte";

// svelte-ignore a11y_click_events_have_key_events

let { params = {} }: { params?: Record<string, string> } = $props();
let slug = $derived(params.slug || "");
let routeKey = $derived(`/projects/${slug}`);

let project = $state<Project | null>(null);
let tasks = $state<Task[]>([]);
Expand All @@ -31,6 +33,14 @@
let filterPriorities = $state<string[]>([]);
let searchQuery = $state("");

let saved = getSnapshot(routeKey);
if (saved) {
filterStatuses = saved.filterStatuses;
filterPriorities = saved.filterPriorities;
sort = saved.sort;
offset = saved.offset;
}

Comment on lines +36 to +43

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make snapshot restoration reactive to route changes.

Svelte 5 component script blocks run only once upon initialization. Because routeKey is derived from dynamic route parameters (params.slug), navigating from one project directly to another reuses the same component instance and updates routeKey reactively, but this top-level initialization block will not re-execute. This causes the new project to incorrectly inherit the previous project's filter state.

To fix this, wrap the restoration in an $effect so it reacts to dynamic parameter changes. Ensure searchQuery and sort are cleared when no snapshot exists to prevent UI state from leaking across projects.

🐛 Proposed fix
-  let saved = getSnapshot(routeKey);
-  if (saved) {
-    filterStatuses = saved.filterStatuses;
-    filterPriorities = saved.filterPriorities;
-    sort = saved.sort;
-    offset = saved.offset;
-  }
+  let currentRouteKey = "";
+
+  $effect(() => {
+    if (routeKey !== currentRouteKey) {
+      currentRouteKey = routeKey;
+      let saved = getSnapshot(routeKey);
+      if (saved) {
+        filterStatuses = saved.filterStatuses;
+        filterPriorities = saved.filterPriorities;
+        sort = saved.sort;
+        offset = saved.offset;
+        searchQuery = ""; // Prevent previous search query from leaking
+      } else {
+        resetFilters();
+        sort = "-created_at"; // Prevent sort order from leaking
+      }
+    }
+  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let saved = getSnapshot(routeKey);
if (saved) {
filterStatuses = saved.filterStatuses;
filterPriorities = saved.filterPriorities;
sort = saved.sort;
offset = saved.offset;
}
let currentRouteKey = "";
$effect(() => {
if (routeKey !== currentRouteKey) {
currentRouteKey = routeKey;
let saved = getSnapshot(routeKey);
if (saved) {
filterStatuses = saved.filterStatuses;
filterPriorities = saved.filterPriorities;
sort = saved.sort;
offset = saved.offset;
searchQuery = ""; // Prevent previous search query from leaking
} else {
resetFilters();
sort = "-created_at"; // Prevent sort order from leaking
}
}
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/lib/pages/project-tasks.svelte` around lines 36 - 43, Wrap the
snapshot restoration logic around getSnapshot(routeKey) in a Svelte 5 $effect so
it reruns whenever the dynamic routeKey changes. Restore all saved state for an
existing snapshot, and explicitly clear searchQuery and sort when no snapshot
exists to prevent state leaking between projects.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Make snapshot restoration reactive to route changes.

Svelte 5 component script blocks only run once on initialization. Because routeKey is derived from dynamic route parameters (params.slug), navigating between projects reuses the component and updates routeKey, but this initialization block will not re-run. This causes the new project to inherit the previous project's state.

Wrap the restoration in an $effect to handle dynamic parameter changes, and ensure searchQuery and sort are cleared to prevent state leaking when no snapshot exists.

🐛 Proposed fix
-  let saved = getSnapshot(routeKey);
-  if (saved) {
-    filterStatuses = saved.filterStatuses;
-    filterPriorities = saved.filterPriorities;
-    sort = saved.sort;
-    offset = saved.offset;
-  }
+  let currentRouteKey = "";
+
+  $effect(() => {
+    if (routeKey !== currentRouteKey) {
+      currentRouteKey = routeKey;
+      let saved = getSnapshot(routeKey);
+      if (saved) {
+        filterStatuses = saved.filterStatuses;
+        filterPriorities = saved.filterPriorities;
+        sort = saved.sort;
+        offset = saved.offset;
+        searchQuery = ""; // Prevent search query from leaking
+      } else {
+        resetFilters();
+        sort = "-created_at"; // Prevent sort order from leaking
+      }
+    }
+  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let saved = getSnapshot(routeKey);
if (saved) {
filterStatuses = saved.filterStatuses;
filterPriorities = saved.filterPriorities;
sort = saved.sort;
offset = saved.offset;
}
let currentRouteKey = "";
$effect(() => {
if (routeKey !== currentRouteKey) {
currentRouteKey = routeKey;
let saved = getSnapshot(routeKey);
if (saved) {
filterStatuses = saved.filterStatuses;
filterPriorities = saved.filterPriorities;
sort = saved.sort;
offset = saved.offset;
searchQuery = ""; // Prevent search query from leaking
} else {
resetFilters();
sort = "-created_at"; // Prevent sort order from leaking
}
}
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/lib/pages/project-tasks.svelte` around lines 36 - 43, Wrap the
snapshot restoration logic around routeKey in an $effect so it re-runs when
dynamic route parameters change. When a snapshot exists, restore its
filterStatuses, filterPriorities, sort, and offset; when none exists, clear
searchQuery and sort to prevent state leaking between projects.

function toggleStatus(s: string) {
if (filterStatuses.includes(s)) {
filterStatuses = filterStatuses.filter((x) => x !== s);
Expand Down Expand Up @@ -84,6 +94,15 @@

$effect(load);

$effect(() => {
saveSnapshot(routeKey, {
filterStatuses: [...filterStatuses],
filterPriorities: [...filterPriorities],
sort,
offset,
});
});

Comment on lines +97 to +105

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Prevent saving old state to the new project during navigation.

When navigating between projects, routeKey updates reactively. If this save $effect executes before the component has loaded the new project's snapshot, it will mistakenly overwrite the new project's storage key with the previous project's filter state.

Add a guard to ensure state is only saved when the route key is stable and fully synchronized with the component state. This implementation assumes currentRouteKey was added as suggested in the previous comment.

🐛 Proposed fix
   $effect(() => {
+    if (routeKey !== currentRouteKey) return;
+
     saveSnapshot(routeKey, {
       filterStatuses: [...filterStatuses],
       filterPriorities: [...filterPriorities],
       sort,
       offset,
     });
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$effect(() => {
saveSnapshot(routeKey, {
filterStatuses: [...filterStatuses],
filterPriorities: [...filterPriorities],
sort,
offset,
});
});
$effect(() => {
if (routeKey !== currentRouteKey) return;
saveSnapshot(routeKey, {
filterStatuses: [...filterStatuses],
filterPriorities: [...filterPriorities],
sort,
offset,
});
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/lib/pages/project-tasks.svelte` around lines 97 - 105, Guard the
snapshot-saving $effect around saveSnapshot so it returns without writing until
currentRouteKey is synchronized with routeKey and the new project’s state has
been loaded. Once both keys match, preserve the existing filterStatuses,
filterPriorities, sort, and offset payload and save it under the stable route
key.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Prevent saving old state to the new project during navigation.

When navigating between projects, routeKey updates reactively. If this $effect runs before the state has been synchronized with the new project's snapshot, it will save the previous project's filter state into the new project's snapshot key.

Add a guard to ensure state is only saved when the route key is stable. This assumes currentRouteKey was added as suggested in the previous comment.

🐛 Proposed fix
   $effect(() => {
+    if (routeKey !== currentRouteKey) return;
+
     saveSnapshot(routeKey, {
       filterStatuses: [...filterStatuses],
       filterPriorities: [...filterPriorities],
       sort,
       offset,
     });
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$effect(() => {
saveSnapshot(routeKey, {
filterStatuses: [...filterStatuses],
filterPriorities: [...filterPriorities],
sort,
offset,
});
});
$effect(() => {
if (routeKey !== currentRouteKey) return;
saveSnapshot(routeKey, {
filterStatuses: [...filterStatuses],
filterPriorities: [...filterPriorities],
sort,
offset,
});
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/lib/pages/project-tasks.svelte` around lines 97 - 105, Guard the
snapshot-saving $effect using currentRouteKey so it only calls saveSnapshot when
currentRouteKey matches the reactive routeKey. Keep the existing filter,
priority, sort, and offset snapshot payload unchanged, preventing the previous
project’s state from being written under the new project key during navigation.

function handleSort(field: string) {
sort = sort === field ? `-${field}` : field;
offset = 0;
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/lib/stores/task-filters.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export type FilterSnapshot = {
filterProject?: string
filterStatuses: string[]
filterPriorities: string[]
sort: string
offset: number
}

let store = $state<Record<string, FilterSnapshot>>({})

export function getSnapshot(path: string): FilterSnapshot | null {
return store[path] ?? null
}

export function saveSnapshot(path: string, snapshot: FilterSnapshot): void {
store[path] = snapshot
}

export function clearSnapshot(path: string): void {
delete store[path]
}
Loading