Gallery's 'Recently edited' sort uses editedAt but the timestamp is updated on every save — user with 50 designs can't tell which ones they've actually opened in the editor vs just saved once #263

Closed
opened 2026-10-04 03:38:38 +00:00 by Mark · 1 comment
Owner

Finding

src/components/gallery/types.ts defines editedAt: number on the Design type. The Gallery's recently-edited sort uses this field.

grep -n 'editedAt' src/components/gallery/saveDesign.ts — let me check what triggers an update.

The saveDesignToGallery function updates editedAt on every save. The user editing a design in the editor, then saving, then making one more change and saving again — both saves update editedAt. The "recently edited" sort shows both as equally recent.

But the user might want to know:

  • "Which designs have I actually OPENED in the editor recently?" (viewed)
  • "Which designs have I actually EDITED (path changes)?" (touched)
  • "Which designs have I just SAVED (without editing)?" (saved)

Currently, all three are conflated. The "Recently edited" sort puts the most-recently-saved at the top, regardless of whether the user actually changed anything.

grep -n 'viewedAt\|lastViewed' src/ returns 0.

Where

  • Design type: src/components/gallery/types.ts (has editedAt, no viewedAt)
  • Sort logic: src/pages/Gallery.tsx (uses editedAt)
  • Save logic: src/components/gallery/saveDesign.ts (updates editedAt)
  • No view tracking: grep -n 'viewedAt' src/ returns 0

Suggested fix shape

  1. Add viewedAt: number | null to the Design type:
export interface Design {
  id: string;
  filename: string;
  // ... existing fields
  editedAt: number;
  viewedAt: number | null;  // new
}
  1. Update viewedAt when the user opens a design (workspace or editor):
// In Gallery's handleLoad
const design = designs.find((d) => d.id === id);
if (design) {
  design.viewedAt = Date.now();
  saveDesigns(designs);
}
  1. Add a "Recently viewed" sort mode:
case 'recently-viewed':
  sorted.sort((a, b) => (b.viewedAt ?? 0) - (a.viewedAt ?? 0));
  break;
  1. Optional: distinguish edit from save:
  • Add lastEditedPathAt: number | null — updated only when path geometry changes
  • "Recently edited (paths)" sort

The simpler version is just the viewedAt. The full version is the 3-way split (viewed / edited / saved).

Severity

Low. The user can read the date on each card. Power users with many designs notice.

Discovered by

Gap analysis (2026-10-02) of gallery sort semantics.

  • The "Recently edited" sort is the baseline; the "Recently viewed" is the addition.
  • The "Created" sort (newest, oldest) is the design's createdAt; the "Edited" sort is the editedAt; the "Viewed" sort is the new viewedAt.
  • The 3-way split is parallel to the file managers' "Modified" vs "Accessed" vs "Created" timestamps.
## Finding `src/components/gallery/types.ts` defines `editedAt: number` on the Design type. The Gallery's `recently-edited` sort uses this field. `grep -n 'editedAt' src/components/gallery/saveDesign.ts` — let me check what triggers an update. The `saveDesignToGallery` function updates `editedAt` on every save. The user editing a design in the editor, then saving, then making one more change and saving again — both saves update `editedAt`. The "recently edited" sort shows both as equally recent. But the user might want to know: - "Which designs have I actually OPENED in the editor recently?" (viewed) - "Which designs have I actually EDITED (path changes)?" (touched) - "Which designs have I just SAVED (without editing)?" (saved) Currently, all three are conflated. The "Recently edited" sort puts the most-recently-saved at the top, regardless of whether the user actually changed anything. `grep -n 'viewedAt\|lastViewed' src/` returns 0. ## Where - Design type: `src/components/gallery/types.ts` (has `editedAt`, no `viewedAt`) - Sort logic: `src/pages/Gallery.tsx` (uses `editedAt`) - Save logic: `src/components/gallery/saveDesign.ts` (updates `editedAt`) - No view tracking: `grep -n 'viewedAt' src/` returns 0 ## Suggested fix shape 1. **Add `viewedAt: number | null` to the Design type**: ```ts export interface Design { id: string; filename: string; // ... existing fields editedAt: number; viewedAt: number | null; // new } ``` 2. **Update `viewedAt` when the user opens a design** (workspace or editor): ```ts // In Gallery's handleLoad const design = designs.find((d) => d.id === id); if (design) { design.viewedAt = Date.now(); saveDesigns(designs); } ``` 3. **Add a "Recently viewed" sort mode**: ```ts case 'recently-viewed': sorted.sort((a, b) => (b.viewedAt ?? 0) - (a.viewedAt ?? 0)); break; ``` 4. **Optional: distinguish edit from save**: - Add `lastEditedPathAt: number | null` — updated only when path geometry changes - "Recently edited (paths)" sort The simpler version is just the viewedAt. The full version is the 3-way split (viewed / edited / saved). ## Severity Low. The user can read the date on each card. Power users with many designs notice. ## Discovered by Gap analysis (2026-10-02) of gallery sort semantics. ## Related - The "Recently edited" sort is the baseline; the "Recently viewed" is the addition. - The "Created" sort (`newest`, `oldest`) is the design's `createdAt`; the "Edited" sort is the `editedAt`; the "Viewed" sort is the new `viewedAt`. - The 3-way split is parallel to the file managers' "Modified" vs "Accessed" vs "Created" timestamps.
Author
Owner

Shipped in commit 6ddb350 (feat(#263)).

  • Added Design.viewedAt field, stamped by Gallery on Workspace / Editor / Export bridge opens.
  • Added recently-viewed sort mode with null-fallback to editedAt so never-opened designs sort by import time.
  • loadDesigns migrates legacy rows (no viewedAt) to null on read.
  • persistedFilters whitelist extended to include the new mode.

Files: src/components/gallery/types.ts, src/components/gallery/data.ts, src/components/gallery/saveDesign.ts, src/components/gallery/persistedFilters.ts, src/pages/Gallery.tsx, plus tests (src/pages/issue-263-viewedAt.test.tsx new, 5 cases; existing data.test.ts + issue-151.test.tsx updated for new field/option).

Verified locally: npx tsc --noEmit clean, npm run lint clean, npm run build clean, 1262 vitest tests pass. CI queued on the self-hosted runner (run #246) — will reconcile async.

Shipped in commit 6ddb350 (`feat(#263)`). - Added `Design.viewedAt` field, stamped by Gallery on Workspace / Editor / Export bridge opens. - Added `recently-viewed` sort mode with null-fallback to `editedAt` so never-opened designs sort by import time. - `loadDesigns` migrates legacy rows (no `viewedAt`) to `null` on read. - `persistedFilters` whitelist extended to include the new mode. Files: `src/components/gallery/types.ts`, `src/components/gallery/data.ts`, `src/components/gallery/saveDesign.ts`, `src/components/gallery/persistedFilters.ts`, `src/pages/Gallery.tsx`, plus tests (`src/pages/issue-263-viewedAt.test.tsx` new, 5 cases; existing `data.test.ts` + `issue-151.test.tsx` updated for new field/option). Verified locally: `npx tsc --noEmit` clean, `npm run lint` clean, `npm run build` clean, 1262 vitest tests pass. CI queued on the self-hosted runner (run #246) — will reconcile async.
Mark closed this issue 2026-10-04 11:54:59 +00:00
Sign in to join this conversation.
No description provided.