["migrate"];
+ /**
+ * Whether to sync changes across tabs/windows using storage events.
+ * @default true
+ */
+ syncAcrossTabs?: boolean;
+};
+
+/**
+ * SolidJS hook for reactive localStorage with Zod schema validation.
+ * Wraps LocalStorageWithSchema in a reactive SolidJS store.
+ *
+ * @example
+ * ```tsx
+ * const [state, setState] = useLocalStorageStore({
+ * key: "myKey",
+ * schema: z.object({value:z.string()}),
+ * fallback: {value:"default"},
+ * });
+ *
+ * return setState("value", "new value")}>{state.value}
;
+ * ```
+ */
+export function useLocalStorageStore(
+ options: UseLocalStorageStoreOptions,
+): [T, SetStoreFunction] {
+ const { key, schema, fallback, migrate, syncAcrossTabs = true } = options;
+
+ // Create the underlying localStorage manager
+ const storage = new LocalStorageWithSchema({
+ key,
+ schema,
+ fallback,
+ migrate,
+ });
+
+ // Create store with initial value from storage
+ const [value, setValue] = createStore(structuredClone(storage.get()));
+
+ // Guard to prevent redundant persist during cross-tab sync
+ let isSyncing = false;
+
+ // Persist entire store to localStorage whenever it changes
+ createEffect(() => {
+ if (!isSyncing) {
+ storage.set(value);
+ }
+ });
+
+ // Sync changes across tabs/windows
+ if (syncAcrossTabs) {
+ const handleStorageChange = (e: StorageEvent): void => {
+ if (e.key === key && e.newValue !== null) {
+ console.debug(`LS ${key} Storage event detected from another tab`);
+ try {
+ const parsed = schema.parse(JSON.parse(e.newValue));
+ isSyncing = true;
+ setValue(reconcile(parsed));
+ isSyncing = false;
+ } catch (error) {
+ console.error(`LS ${key} Failed to parse storage event value`, error);
+ }
+ }
+ };
+
+ window.addEventListener("storage", handleStorageChange);
+ onCleanup(() => {
+ window.removeEventListener("storage", handleStorageChange);
+ });
+ }
+
+ return [value, setValue];
+}
diff --git a/frontend/src/ts/signals/core.ts b/frontend/src/ts/signals/core.ts
index f3c6127b08e0..175691f4316d 100644
--- a/frontend/src/ts/signals/core.ts
+++ b/frontend/src/ts/signals/core.ts
@@ -29,3 +29,6 @@ export const [getCommandlineSubgroup, setCommandlineSubgroup] = createSignal<
export const [getFocus, setFocus] = createSignal(false);
export const [getGlobalOffsetTop, setGlobalOffsetTop] = createSignal(0);
export const [getIsScreenshotting, setIsScreenshotting] = createSignal(false);
+
+export const [getUserId, setUserId] = createSignal(null);
+export const isLoggedIn = () => getUserId() !== null;
diff --git a/frontend/src/ts/utils/local-storage-with-schema.ts b/frontend/src/ts/utils/local-storage-with-schema.ts
index 00365b510115..a52a4b41b86a 100644
--- a/frontend/src/ts/utils/local-storage-with-schema.ts
+++ b/frontend/src/ts/utils/local-storage-with-schema.ts
@@ -91,9 +91,12 @@ export class LocalStorageWithSchema {
try {
console.debug(`LS ${this.key} Parsing to set in localStorage`);
const parsed = this.schema.parse(data);
- console.debug(`LS ${this.key} Setting in localStorage`);
- window.localStorage.setItem(this.key, JSON.stringify(parsed));
- this.cache = parsed;
+ const newValue = JSON.stringify(parsed);
+ if (newValue !== JSON.stringify(this.cache)) {
+ console.debug(`LS ${this.key} Setting in localStorage`);
+ window.localStorage.setItem(this.key, newValue);
+ this.cache = parsed;
+ }
return true;
} catch (e) {
let message = "Unknown error occurred";
diff --git a/monkeytype.code-workspace b/monkeytype.code-workspace
index 7fd0101d54a9..335d042b84a0 100644
--- a/monkeytype.code-workspace
+++ b/monkeytype.code-workspace
@@ -45,6 +45,7 @@
"vitest.maximumConfigs": 10,
"oxc.typeAware": true,
"typescript.format.enable": false,
+ "oxc.fmt.configPath": ".oxfmtrc-editor.json",
"[json]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
},
diff --git a/packages/.vscode/settings.json b/packages/.vscode/settings.json
new file mode 100644
index 000000000000..02015d68e590
--- /dev/null
+++ b/packages/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "oxc.fmt.configPath": "../.oxfmtrc-editor.json"
+}