-
Notifications
You must be signed in to change notification settings - Fork 20
Issue 199 implementation #628
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: staging
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| export const RUBRIC_UNSAVED_CHANGES_WARNING_MESSAGE = | ||
| "You have unsaved rubric changes. Leave this page without saving?"; | ||
|
|
||
| const STORAGE_KEY_PREFIX = "pawtograder:rubric-unsaved-changes"; | ||
|
|
||
| function getStorageKeyOrNull(assignmentId: string | number): string | null { | ||
| const normalizedAssignmentId = String(assignmentId).trim(); | ||
| if (!normalizedAssignmentId) return null; | ||
| return `${STORAGE_KEY_PREFIX}:${normalizedAssignmentId}`; | ||
| } | ||
|
|
||
| export function setRubricUnsavedChangesFlag(assignmentId: string | number, hasUnsavedChanges: boolean): void { | ||
| if (typeof window === "undefined") return; | ||
| const storageKey = getStorageKeyOrNull(assignmentId); | ||
| if (!storageKey) return; | ||
|
|
||
| if (hasUnsavedChanges) { | ||
| window.sessionStorage.setItem(storageKey, "true"); | ||
| return; | ||
| } | ||
| window.sessionStorage.removeItem(storageKey); | ||
| } | ||
|
Comment on lines
+12
to
+22
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n lib/rubricUnsavedChanges.tsRepository: pawtograder/platform Length of output: 1696 🌐 Web query:
💡 Result: In Safari, What to do
export function storageAvailable(kind = "sessionStorage") {
try {
const s = window[kind]; // may throw in Safari
const k = "__storage_test__";
s.setItem(k, "1"); // may throw QuotaExceededError (quota=0)
s.removeItem(k);
return true;
} catch {
return false;
}
}
// usage
const mem = new Map();
export const store = {
get(key) {
if (storageAvailable("sessionStorage")) return sessionStorage.getItem(key);
return mem.get(key) ?? null;
},
set(key, val) {
if (storageAvailable("sessionStorage")) sessionStorage.setItem(key, val);
else mem.set(key, String(val));
}
};If you tell me the exact Safari version + platform (macOS/iOS) + the thrown error text ( Sources: [1][2][3] 🏁 Script executed: rg "sessionStorage" --type ts --type tsx --type js --type jsxRepository: pawtograder/platform Length of output: 91 🏁 Script executed: rg "sessionStorage" --type ts --type jsRepository: pawtograder/platform Length of output: 2002 Harden storage access to avoid runtime crashes in restricted browser storage contexts. Direct Proposed fix+function getSessionStorageOrNull(): Storage | null {
+ if (typeof window === "undefined") return null;
+ try {
+ return window.sessionStorage;
+ } catch {
+ return null;
+ }
+}
+
export function setRubricUnsavedChangesFlag(assignmentId: string | number, hasUnsavedChanges: boolean): void {
- if (typeof window === "undefined") return;
+ const storage = getSessionStorageOrNull();
+ if (!storage) return;
const storageKey = getStorageKeyOrNull(assignmentId);
if (!storageKey) return;
if (hasUnsavedChanges) {
- window.sessionStorage.setItem(storageKey, "true");
+ storage.setItem(storageKey, "true");
return;
}
- window.sessionStorage.removeItem(storageKey);
+ storage.removeItem(storageKey);
}
export function hasRubricUnsavedChangesFlag(assignmentId: string | number): boolean {
- if (typeof window === "undefined") return false;
+ const storage = getSessionStorageOrNull();
+ if (!storage) return false;
const storageKey = getStorageKeyOrNull(assignmentId);
if (!storageKey) return false;
- return window.sessionStorage.getItem(storageKey) === "true";
+ return storage.getItem(storageKey) === "true";
}
export function clearRubricUnsavedChangesFlag(assignmentId: string | number): void {
- if (typeof window === "undefined") return;
+ const storage = getSessionStorageOrNull();
+ if (!storage) return;
const storageKey = getStorageKeyOrNull(assignmentId);
if (!storageKey) return;
- window.sessionStorage.removeItem(storageKey);
+ storage.removeItem(storageKey);
}Also applies to: 24-29, 31-36 🤖 Prompt for AI Agents |
||
|
|
||
| export function hasRubricUnsavedChangesFlag(assignmentId: string | number): boolean { | ||
| if (typeof window === "undefined") return false; | ||
| const storageKey = getStorageKeyOrNull(assignmentId); | ||
| if (!storageKey) return false; | ||
| return window.sessionStorage.getItem(storageKey) === "true"; | ||
| } | ||
|
|
||
| export function clearRubricUnsavedChangesFlag(assignmentId: string | number): void { | ||
| if (typeof window === "undefined") return; | ||
| const storageKey = getStorageKeyOrNull(assignmentId); | ||
| if (!storageKey) return; | ||
| window.sessionStorage.removeItem(storageKey); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import { | ||
| clearRubricUnsavedChangesFlag, | ||
| hasRubricUnsavedChangesFlag, | ||
| setRubricUnsavedChangesFlag | ||
| } from "@/lib/rubricUnsavedChanges"; | ||
|
|
||
| describe("rubric unsaved changes session state", () => { | ||
| const assignmentId = "123"; | ||
|
|
||
| beforeEach(() => { | ||
| window.sessionStorage.clear(); | ||
| }); | ||
|
|
||
| it("sets and reads unsaved state", () => { | ||
| setRubricUnsavedChangesFlag(assignmentId, true); | ||
| expect(hasRubricUnsavedChangesFlag(assignmentId)).toBe(true); | ||
| }); | ||
|
|
||
| it("clears unsaved state when set false", () => { | ||
| setRubricUnsavedChangesFlag(assignmentId, true); | ||
| setRubricUnsavedChangesFlag(assignmentId, false); | ||
| expect(hasRubricUnsavedChangesFlag(assignmentId)).toBe(false); | ||
| }); | ||
|
|
||
| it("clears unsaved state explicitly", () => { | ||
| setRubricUnsavedChangesFlag(assignmentId, true); | ||
| clearRubricUnsavedChangesFlag(assignmentId); | ||
| expect(hasRubricUnsavedChangesFlag(assignmentId)).toBe(false); | ||
| }); | ||
|
|
||
| it("ignores empty assignment ids", () => { | ||
| setRubricUnsavedChangesFlag("", true); | ||
| expect(hasRubricUnsavedChangesFlag("")).toBe(false); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Visibility ownership handoff is not reactive to breakpoint-only visibility changes.
This effect won’t re-run when the instance switches hidden/visible via CSS alone, so the session flag owner can stay stale until some other dependency changes.
Suggested direction
Also applies to: 901-909
🤖 Prompt for AI Agents