diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts
index 0e991cdfb547947254ba0f6fdaccf1fb1ff6093d..93916ccf2fdc2a13a3837c07bb5462d249f50d48 100644
--- a/packages/frontend/src/account.ts
+++ b/packages/frontend/src/account.ts
@@ -6,12 +6,13 @@ import { del, get, set } from '@/scripts/idb-proxy';
 import { apiUrl } from '@/config';
 import { waiting, api, popup, popupMenu, success, alert } from '@/os';
 import { unisonReload, reloadChannel } from '@/scripts/unison-reload';
+import { miLocalStorage } from './local-storage';
 
 // TODO: 他のタブと永続化されたstateを同期
 
 type Account = misskey.entities.MeDetailed;
 
-const accountData = localStorage.getItem('account');
+const accountData = miLocalStorage.getItem('account');
 
 // TODO: 外部からはreadonlyに
 export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null;
@@ -21,7 +22,7 @@ export const iAmAdmin = $i != null && $i.isAdmin;
 
 export async function signout() {
 	waiting();
-	localStorage.removeItem('account');
+	miLocalStorage.removeItem('account');
 
 	await removeAccount($i.id);
 
@@ -119,7 +120,7 @@ export function updateAccount(accountData) {
 	for (const [key, value] of Object.entries(accountData)) {
 		$i[key] = value;
 	}
-	localStorage.setItem('account', JSON.stringify($i));
+	miLocalStorage.setItem('account', JSON.stringify($i));
 }
 
 export function refreshAccount() {
@@ -130,7 +131,7 @@ export async function login(token: Account['token'], redirect?: string) {
 	waiting();
 	if (_DEV_) console.log('logging as token ', token);
 	const me = await fetchAccount(token);
-	localStorage.setItem('account', JSON.stringify(me));
+	miLocalStorage.setItem('account', JSON.stringify(me));
 	document.cookie = `token=${token}; path=/; max-age=31536000`; // bull dashboardの認証とかで使う
 	await addAccount(me.id, token);
 
diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue
index 08e2c29de29419e68b889cf86f8fa79fb4acc3c9..8ed60bc5dc071d7c932e0ae3ca7b79c689368f46 100644
--- a/packages/frontend/src/components/MkAutocomplete.vue
+++ b/packages/frontend/src/components/MkAutocomplete.vue
@@ -46,6 +46,7 @@ import { defaultStore } from '@/store';
 import { emojilist } from '@/scripts/emojilist';
 import { instance } from '@/instance';
 import { i18n } from '@/i18n';
+import { miLocalStorage } from '@/local-storage';
 
 type EmojiDef = {
 	emoji: string;
@@ -208,7 +209,7 @@ function exec() {
 		}
 	} else if (props.type === 'hashtag') {
 		if (!props.q || props.q === '') {
-			hashtags.value = JSON.parse(localStorage.getItem('hashtags') || '[]');
+			hashtags.value = JSON.parse(miLocalStorage.getItem('hashtags') || '[]');
 			fetching.value = false;
 		} else {
 			const cacheKey = `autocomplete:hashtag:${props.q}`;
diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue
index 5a406c8635f85fc3163d32322ca4da8e5560bbcd..dc10c7d3f3b6e1c19538b338083e7fd7c24d9ede 100644
--- a/packages/frontend/src/components/MkFolder.vue
+++ b/packages/frontend/src/components/MkFolder.vue
@@ -25,8 +25,9 @@
 <script lang="ts">
 import { defineComponent } from 'vue';
 import tinycolor from 'tinycolor2';
+import { miLocalStorage } from '@/local-storage';
 
-const localStoragePrefix = 'ui:folder:';
+const miLocalStoragePrefix = 'ui:folder:' as const;
 
 export default defineComponent({
 	props: {
@@ -44,13 +45,13 @@ export default defineComponent({
 	data() {
 		return {
 			bg: null,
-			showBody: (this.persistKey && localStorage.getItem(localStoragePrefix + this.persistKey)) ? localStorage.getItem(localStoragePrefix + this.persistKey) === 't' : this.expanded,
+			showBody: (this.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${this.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${this.persistKey}`) === 't') : this.expanded,
 		};
 	},
 	watch: {
 		showBody() {
 			if (this.persistKey) {
-				localStorage.setItem(localStoragePrefix + this.persistKey, this.showBody ? 't' : 'f');
+				miLocalStorage.setItem(`${miLocalStoragePrefix}${this.persistKey}`, this.showBody ? 't' : 'f');
 			}
 		},
 	},
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 883ad9f14f2d2211b598a856658cbace74d40b13..ff3b7ec1f594be5a7d7272f281319656c35f7357 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -98,6 +98,7 @@ import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account'
 import { uploadFile } from '@/scripts/upload';
 import { deepClone } from '@/scripts/clone';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
+import { miLocalStorage } from '@/local-storage';
 
 const modal = inject('modal');
 
@@ -156,7 +157,7 @@ let autocomplete = $ref(null);
 let draghover = $ref(false);
 let quoteId = $ref(null);
 let hasNotSpecifiedMentions = $ref(false);
-let recentHashtags = $ref(JSON.parse(localStorage.getItem('hashtags') || '[]'));
+let recentHashtags = $ref(JSON.parse(miLocalStorage.getItem('hashtags') || '[]'));
 let imeText = $ref('');
 
 const typing = throttle(3000, () => {
@@ -543,7 +544,7 @@ function onDrop(ev): void {
 }
 
 function saveDraft() {
-	const draftData = JSON.parse(localStorage.getItem('drafts') || '{}');
+	const draftData = JSON.parse(miLocalStorage.getItem('drafts') || '{}');
 
 	draftData[draftKey] = {
 		updatedAt: new Date(),
@@ -558,15 +559,15 @@ function saveDraft() {
 		},
 	};
 
-	localStorage.setItem('drafts', JSON.stringify(draftData));
+	miLocalStorage.setItem('drafts', JSON.stringify(draftData));
 }
 
 function deleteDraft() {
-	const draftData = JSON.parse(localStorage.getItem('drafts') ?? '{}');
+	const draftData = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}');
 
 	delete draftData[draftKey];
 
-	localStorage.setItem('drafts', JSON.stringify(draftData));
+	miLocalStorage.setItem('drafts', JSON.stringify(draftData));
 }
 
 async function post(ev?: MouseEvent) {
@@ -622,8 +623,8 @@ async function post(ev?: MouseEvent) {
 			emit('posted');
 			if (postData.text && postData.text !== '') {
 				const hashtags_ = mfm.parse(postData.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag);
-				const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[];
-				localStorage.setItem('hashtags', JSON.stringify(unique(hashtags_.concat(history))));
+				const history = JSON.parse(miLocalStorage.getItem('hashtags') || '[]') as string[];
+				miLocalStorage.setItem('hashtags', JSON.stringify(unique(hashtags_.concat(history))));
 			}
 			posting = false;
 			postAccount = null;
@@ -698,7 +699,7 @@ onMounted(() => {
 	nextTick(() => {
 		// 書きかけの投稿を復元
 		if (!props.instant && !props.mention && !props.specified) {
-			const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[draftKey];
+			const draft = JSON.parse(miLocalStorage.getItem('drafts') || '{}')[draftKey];
 			if (draft) {
 				text = draft.data.text;
 				useCw = draft.data.useCw;
diff --git a/packages/frontend/src/config.ts b/packages/frontend/src/config.ts
index f2022b0f02104123fe5249928a9064b70c5886c3..4b084d365b29aaf2103a3114fbdde0f5a7a0a638 100644
--- a/packages/frontend/src/config.ts
+++ b/packages/frontend/src/config.ts
@@ -1,3 +1,5 @@
+import { miLocalStorage } from "./local-storage";
+
 const address = new URL(location.href);
 const siteName = (document.querySelector('meta[property="og:site_name"]') as HTMLMetaElement)?.content;
 
@@ -6,10 +8,10 @@ export const hostname = address.hostname;
 export const url = address.origin;
 export const apiUrl = url + '/api';
 export const wsUrl = url.replace('http://', 'ws://').replace('https://', 'wss://') + '/streaming';
-export const lang = localStorage.getItem('lang');
+export const lang = miLocalStorage.getItem('lang');
 export const langs = _LANGS_;
-export const locale = JSON.parse(localStorage.getItem('locale'));
+export const locale = JSON.parse(miLocalStorage.getItem('locale'));
 export const version = _VERSION_;
 export const instanceName = siteName === 'Misskey' ? host : siteName;
-export const ui = localStorage.getItem('ui');
-export const debug = localStorage.getItem('debug') === 'true';
+export const ui = miLocalStorage.getItem('ui');
+export const debug = miLocalStorage.getItem('debug') === 'true';
diff --git a/packages/frontend/src/init.ts b/packages/frontend/src/init.ts
index 45ade64127904458bcac266f40d41ed4ad9d0e69..bd515f47ea6854893454344d3b9d5636d501502f 100644
--- a/packages/frontend/src/init.ts
+++ b/packages/frontend/src/init.ts
@@ -9,9 +9,12 @@ import '@/style.scss';
 //#region account indexedDB migration
 import { set } from '@/scripts/idb-proxy';
 
-if (localStorage.getItem('accounts') != null) {
-	set('accounts', JSON.parse(localStorage.getItem('accounts')));
-	localStorage.removeItem('accounts');
+{
+	const accounts = miLocalStorage.getItem('accounts');
+	if (accounts) {
+		set('accounts', JSON.parse(accounts));
+		miLocalStorage.removeItem('accounts');
+	}
 }
 //#endregion
 
@@ -40,6 +43,7 @@ import { reloadChannel } from '@/scripts/unison-reload';
 import { reactionPicker } from '@/scripts/reaction-picker';
 import { getUrlWithoutLoginId } from '@/scripts/login-id';
 import { getAccountFromId } from '@/scripts/get-account-from-id';
+import { miLocalStorage } from './local-storage';
 
 (async () => {
 	console.info(`Misskey v${version}`);
@@ -154,7 +158,7 @@ import { getAccountFromId } from '@/scripts/get-account-from-id';
 	const fetchInstanceMetaPromise = fetchInstance();
 
 	fetchInstanceMetaPromise.then(() => {
-		localStorage.setItem('v', instance.version);
+		miLocalStorage.setItem('v', instance.version);
 
 		// Init service worker
 		initializeSw();
@@ -223,12 +227,12 @@ import { getAccountFromId } from '@/scripts/get-account-from-id';
 	}
 
 	// クライアントが更新されたか?
-	const lastVersion = localStorage.getItem('lastVersion');
+	const lastVersion = miLocalStorage.getItem('lastVersion');
 	if (lastVersion !== version) {
-		localStorage.setItem('lastVersion', version);
+		miLocalStorage.setItem('lastVersion', version);
 
 		// テーマリビルドするため
-		localStorage.removeItem('theme');
+		miLocalStorage.removeItem('theme');
 
 		try { // 変なバージョン文字列来るとcompareVersionsでエラーになるため
 			if (lastVersion != null && compareVersions(version, lastVersion) === 1) {
@@ -244,7 +248,7 @@ import { getAccountFromId } from '@/scripts/get-account-from-id';
 	// NOTE: この処理は必ず↑のクライアント更新時処理より後に来ること(テーマ再構築のため)
 	watch(defaultStore.reactiveState.darkMode, (darkMode) => {
 		applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme'));
-	}, { immediate: localStorage.theme == null });
+	}, { immediate: miLocalStorage.getItem('theme') == null });
 
 	const darkTheme = computed(ColdDeviceStorage.makeGetterSetter('darkTheme'));
 	const lightTheme = computed(ColdDeviceStorage.makeGetterSetter('lightTheme'));
@@ -341,7 +345,7 @@ import { getAccountFromId } from '@/scripts/get-account-from-id';
 			});
 		}
 
-		const lastUsed = localStorage.getItem('lastUsed');
+		const lastUsed = miLocalStorage.getItem('lastUsed');
 		if (lastUsed) {
 			const lastUsedDate = parseInt(lastUsed, 10);
 			// 二時間以上前なら
@@ -351,7 +355,7 @@ import { getAccountFromId } from '@/scripts/get-account-from-id';
 				}));
 			}
 		}
-		localStorage.setItem('lastUsed', Date.now().toString());
+		miLocalStorage.setItem('lastUsed', Date.now().toString());
 
 		if ('Notification' in window) {
 			// 許可を得ていなかったらリクエスト
diff --git a/packages/frontend/src/instance.ts b/packages/frontend/src/instance.ts
index 51464f32fb2ba817a1d2e0f697881ab769011b19..82d3e7aea2902deb7e099a8ec415343ddaba3a43 100644
--- a/packages/frontend/src/instance.ts
+++ b/packages/frontend/src/instance.ts
@@ -1,10 +1,11 @@
 import { computed, reactive } from 'vue';
 import * as Misskey from 'misskey-js';
 import { api } from './os';
+import { miLocalStorage } from './local-storage';
 
 // TODO: 他のタブと永続化されたstateを同期
 
-const instanceData = localStorage.getItem('instance');
+const instanceData = miLocalStorage.getItem('instance');
 
 // TODO: instanceをリアクティブにするかは再考の余地あり
 
@@ -21,7 +22,7 @@ export async function fetchInstance() {
 		instance[k] = v;
 	}
 
-	localStorage.setItem('instance', JSON.stringify(instance));
+	miLocalStorage.setItem('instance', JSON.stringify(instance));
 }
 
 export const emojiCategories = computed(() => {
diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5286a6f3a7bb3159eca5523893358cca9de5151f
--- /dev/null
+++ b/packages/frontend/src/local-storage.ts
@@ -0,0 +1,31 @@
+type Keys =
+	'v' |
+	'lastVersion' |
+	'instance' |
+	'account' |
+	'accounts' |
+	'lastUsed' |
+	'lang' |
+	'drafts' |
+	'hashtags' |
+	'wallpaper' |
+	'theme' |
+	'colorSchema' |
+	'useSystemFont' | 
+	'fontSize' |
+	'ui' |
+	'locale' |
+	'theme' |
+	'customCss' |
+	'message_drafts' |
+	'scratchpad' |
+	`miux:${string}` |
+	`ui:folder:${string}` |
+	`themes:${string}` |
+	`aiscript:${string}`;
+
+export const miLocalStorage = {
+	getItem: (key: Keys) => window.localStorage.getItem(key),
+	setItem: (key: Keys, value: string) => window.localStorage.setItem(key, value),
+	removeItem: (key: Keys) => window.localStorage.removeItem(key),
+};
diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts
index efc0abfc6e78385b34268967f3a046e5267fe305..9ee78741dc474328a69f02ad61c84be870d22fe1 100644
--- a/packages/frontend/src/navbar.ts
+++ b/packages/frontend/src/navbar.ts
@@ -5,6 +5,7 @@ import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { ui } from '@/config';
 import { unisonReload } from '@/scripts/unison-reload';
+import { miLocalStorage } from './local-storage';
 
 export const navbarItemDef = reactive({
 	notifications: {
@@ -110,21 +111,21 @@ export const navbarItemDef = reactive({
 				text: i18n.ts.default,
 				active: ui === 'default' || ui === null,
 				action: () => {
-					localStorage.setItem('ui', 'default');
+					miLocalStorage.setItem('ui', 'default');
 					unisonReload();
 				},
 			}, {
 				text: i18n.ts.deck,
 				active: ui === 'deck',
 				action: () => {
-					localStorage.setItem('ui', 'deck');
+					miLocalStorage.setItem('ui', 'deck');
 					unisonReload();
 				},
 			}, {
 				text: i18n.ts.classic,
 				active: ui === 'classic',
 				action: () => {
-					localStorage.setItem('ui', 'classic');
+					miLocalStorage.setItem('ui', 'classic');
 					unisonReload();
 				},
 			}], ev.currentTarget ?? ev.target);
diff --git a/packages/frontend/src/pages/_error_.vue b/packages/frontend/src/pages/_error_.vue
index da2889ba2705e5743bf24be52bee1fd9e4d14fe1..5001b5a8b4d1e91c52e648a18cad368bcc56d10f 100644
--- a/packages/frontend/src/pages/_error_.vue
+++ b/packages/frontend/src/pages/_error_.vue
@@ -26,6 +26,7 @@ import * as os from '@/os';
 import { unisonReload } from '@/scripts/unison-reload';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
+import { miLocalStorage } from '@/local-storage';
 
 const props = withDefaults(defineProps<{
 	error?: Error;
@@ -42,7 +43,7 @@ os.api('meta', {
 	loaded = true;
 	serverIsDead = false;
 	meta = res;
-	localStorage.setItem('v', res.version);
+	miLocalStorage.setItem('v', res.version);
 }, () => {
 	loaded = true;
 	serverIsDead = true;
diff --git a/packages/frontend/src/pages/messaging/messaging-room.form.vue b/packages/frontend/src/pages/messaging/messaging-room.form.vue
index a0fa2677d008f9b95985b4fa41f5e7c81ad352b7..e880129033be0eb4990b5ff768c5bb0c34036c94 100644
--- a/packages/frontend/src/pages/messaging/messaging-room.form.vue
+++ b/packages/frontend/src/pages/messaging/messaging-room.form.vue
@@ -40,6 +40,7 @@ import { defaultStore } from '@/store';
 import { i18n } from '@/i18n';
 //import { Autocomplete } from '@/scripts/autocomplete';
 import { uploadFile } from '@/scripts/upload';
+import { miLocalStorage } from '@/local-storage';
 
 const props = defineProps<{
 	user?: Misskey.entities.UserDetailed | null;
@@ -188,7 +189,7 @@ function clear() {
 }
 
 function saveDraft() {
-	const drafts = JSON.parse(localStorage.getItem('message_drafts') || '{}');
+	const drafts = JSON.parse(miLocalStorage.getItem('message_drafts') || '{}');
 
 	drafts[draftKey] = {
 		updatedAt: new Date(),
@@ -199,15 +200,15 @@ function saveDraft() {
 		},
 	};
 
-	localStorage.setItem('message_drafts', JSON.stringify(drafts));
+	miLocalStorage.setItem('message_drafts', JSON.stringify(drafts));
 }
 
 function deleteDraft() {
-	const drafts = JSON.parse(localStorage.getItem('message_drafts') || '{}');
+	const drafts = JSON.parse(miLocalStorage.getItem('message_drafts') || '{}');
 
 	delete drafts[draftKey];
 
-	localStorage.setItem('message_drafts', JSON.stringify(drafts));
+	miLocalStorage.setItem('message_drafts', JSON.stringify(drafts));
 }
 
 async function insertEmoji(ev: MouseEvent) {
@@ -222,7 +223,7 @@ onMounted(() => {
 	//new Autocomplete(textEl, this, { model: 'text' });
 
 	// 書きかけの投稿を復元
-	const draft = JSON.parse(localStorage.getItem('message_drafts') || '{}')[draftKey];
+	const draft = JSON.parse(miLocalStorage.getItem('message_drafts') || '{}')[draftKey];
 	if (draft) {
 		text = draft.data.text;
 		file = draft.data.file;
diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue
index 316133b96807d70ff666d0a4f9ee97ceaf39fa78..ff5f06c8da8c68236614ab1054ffbcece838b627 100644
--- a/packages/frontend/src/pages/scratchpad.vue
+++ b/packages/frontend/src/pages/scratchpad.vue
@@ -46,6 +46,7 @@ import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import { AsUiComponent, AsUiRoot, patch, registerAsUiLib, render } from '@/scripts/aiscript/ui';
 import MkAsUi from '@/components/MkAsUi.vue';
+import { miLocalStorage } from '@/local-storage';
 
 const parser = new Parser();
 let aiscript: Interpreter;
@@ -55,13 +56,13 @@ const root = ref<AsUiRoot>();
 let components: Ref<AsUiComponent>[] = [];
 let uiKey = $ref(0);
 
-const saved = localStorage.getItem('scratchpad');
+const saved = miLocalStorage.getItem('scratchpad');
 if (saved) {
 	code.value = saved;
 }
 
 watch(code, () => {
-	localStorage.setItem('scratchpad', code.value);
+	miLocalStorage.setItem('scratchpad', code.value);
 });
 
 async function run() {
diff --git a/packages/frontend/src/pages/settings/custom-css.vue b/packages/frontend/src/pages/settings/custom-css.vue
index 9fa9bdd6585531dc2484ad543abfa89a4f0b9b2a..be2ec32ac2e8e90e8aac12704ac1c1a8baee42cf 100644
--- a/packages/frontend/src/pages/settings/custom-css.vue
+++ b/packages/frontend/src/pages/settings/custom-css.vue
@@ -16,11 +16,12 @@ import * as os from '@/os';
 import { unisonReload } from '@/scripts/unison-reload';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
+import { miLocalStorage } from '@/local-storage';
 
-const localCustomCss = ref(localStorage.getItem('customCss') ?? '');
+const localCustomCss = ref(miLocalStorage.getItem('customCss') ?? '');
 
 async function apply() {
-	localStorage.setItem('customCss', localCustomCss.value);
+	miLocalStorage.setItem('customCss', localCustomCss.value);
 
 	const { canceled } = await os.confirm({
 		type: 'info',
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index b90dc3da0e4a479b079f8fce09d27e40af6f8cc8..580c38149a3aca374207709d8318ed1ec7f2a657 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -120,10 +120,11 @@ import * as os from '@/os';
 import { unisonReload } from '@/scripts/unison-reload';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
+import { miLocalStorage } from '@/local-storage';
 
-const lang = ref(localStorage.getItem('lang'));
-const fontSize = ref(localStorage.getItem('fontSize'));
-const useSystemFont = ref(localStorage.getItem('useSystemFont') != null);
+const lang = ref(miLocalStorage.getItem('lang'));
+const fontSize = ref(miLocalStorage.getItem('fontSize'));
+const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
 
 async function reloadAsk() {
 	const { canceled } = await os.confirm({
@@ -157,23 +158,23 @@ const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars'));
 const aiChanMode = computed(defaultStore.makeGetterSetter('aiChanMode'));
 
 watch(lang, () => {
-	localStorage.setItem('lang', lang.value as string);
-	localStorage.removeItem('locale');
+	miLocalStorage.setItem('lang', lang.value as string);
+	miLocalStorage.removeItem('locale');
 });
 
 watch(fontSize, () => {
 	if (fontSize.value == null) {
-		localStorage.removeItem('fontSize');
+		miLocalStorage.removeItem('fontSize');
 	} else {
-		localStorage.setItem('fontSize', fontSize.value);
+		miLocalStorage.setItem('fontSize', fontSize.value);
 	}
 });
 
 watch(useSystemFont, () => {
 	if (useSystemFont.value) {
-		localStorage.setItem('useSystemFont', 't');
+		miLocalStorage.setItem('useSystemFont', 't');
 	} else {
-		localStorage.removeItem('useSystemFont');
+		miLocalStorage.removeItem('useSystemFont');
 	}
 });
 
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index 119a75b6507bae2d28dec4f39ecd21fe527792ac..3468d44e00b6e02f12b13629104e90626f2a9b3d 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -33,6 +33,7 @@ import { instance } from '@/instance';
 import { useRouter } from '@/router';
 import { definePageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata';
 import * as os from '@/os';
+import { miLocalStorage } from '@/local-storage';
 
 const indexInfo = {
 	title: i18n.ts.settings,
@@ -180,8 +181,8 @@ const menuDef = computed(() => [{
 		icon: 'ti ti-trash',
 		text: i18n.ts.clearCache,
 		action: () => {
-			localStorage.removeItem('locale');
-			localStorage.removeItem('theme');
+			miLocalStorage.removeItem('locale');
+			miLocalStorage.removeItem('theme');
 			unisonReload();
 		},
 	}, {
diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue
index 0c32676c8947d9752860d847a663279e6cf827e1..87a08612fc89aed2b84b3aa98721076bf8838c41 100644
--- a/packages/frontend/src/pages/settings/preferences-backups.vue
+++ b/packages/frontend/src/pages/settings/preferences-backups.vue
@@ -45,6 +45,7 @@ import { $i } from '@/account';
 import { i18n } from '@/i18n';
 import { version, host } from '@/config';
 import { definePageMetadata } from '@/scripts/page-metadata';
+import { miLocalStorage } from '@/local-storage';
 const { t, ts } = i18n;
 
 useCssModule();
@@ -170,9 +171,9 @@ function getSettings(): Profile['settings'] {
 	return {
 		hot,
 		cold,
-		fontSize: localStorage.getItem('fontSize'),
-		useSystemFont: localStorage.getItem('useSystemFont') as 't' | null,
-		wallpaper: localStorage.getItem('wallpaper'),
+		fontSize: miLocalStorage.getItem('fontSize'),
+		useSystemFont: miLocalStorage.getItem('useSystemFont') as 't' | null,
+		wallpaper: miLocalStorage.getItem('wallpaper'),
 	};
 }
 
@@ -279,23 +280,23 @@ async function applyProfile(id: string): Promise<void> {
 
 	// fontSize
 	if (settings.fontSize) {
-		localStorage.setItem('fontSize', settings.fontSize);
+		miLocalStorage.setItem('fontSize', settings.fontSize);
 	} else {
-		localStorage.removeItem('fontSize');
+		miLocalStorage.removeItem('fontSize');
 	}
 
 	// useSystemFont
 	if (settings.useSystemFont) {
-		localStorage.setItem('useSystemFont', settings.useSystemFont);
+		miLocalStorage.setItem('useSystemFont', settings.useSystemFont);
 	} else {
-		localStorage.removeItem('useSystemFont');
+		miLocalStorage.removeItem('useSystemFont');
 	}
 
 	// wallpaper
 	if (settings.wallpaper != null) {
-		localStorage.setItem('wallpaper', settings.wallpaper);
+		miLocalStorage.setItem('wallpaper', settings.wallpaper);
 	} else {
-		localStorage.removeItem('wallpaper');
+		miLocalStorage.removeItem('wallpaper');
 	}
 
 	const { canceled: cancel2 } = await os.confirm({
diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue
index 60aa80647d4f815c4baa340b40cf1f30f0b740e3..a2dc9bc95f82c204e7d8f8e82d801c4f1cb95894 100644
--- a/packages/frontend/src/pages/settings/theme.vue
+++ b/packages/frontend/src/pages/settings/theme.vue
@@ -82,6 +82,7 @@ import { instance } from '@/instance';
 import { uniqueBy } from '@/scripts/array';
 import { fetchThemes, getThemes } from '@/theme-store';
 import { definePageMetadata } from '@/scripts/page-metadata';
+import { miLocalStorage } from '@/local-storage';
 
 const installedThemes = ref(getThemes());
 const builtinThemes = getBuiltinThemesRef();
@@ -120,7 +121,7 @@ const lightThemeId = computed({
 });
 const darkMode = computed(defaultStore.makeGetterSetter('darkMode'));
 const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode'));
-const wallpaper = ref(localStorage.getItem('wallpaper'));
+const wallpaper = ref(miLocalStorage.getItem('wallpaper'));
 const themesCount = installedThemes.value.length;
 
 watch(syncDeviceDarkMode, () => {
@@ -131,9 +132,9 @@ watch(syncDeviceDarkMode, () => {
 
 watch(wallpaper, () => {
 	if (wallpaper.value == null) {
-		localStorage.removeItem('wallpaper');
+		miLocalStorage.removeItem('wallpaper');
 	} else {
-		localStorage.setItem('wallpaper', wallpaper.value);
+		miLocalStorage.setItem('wallpaper', wallpaper.value);
 	}
 	location.reload();
 });
diff --git a/packages/frontend/src/scripts/aiscript/api.ts b/packages/frontend/src/scripts/aiscript/api.ts
index 6debcb8a139336d02d32dcaf1465b012ab648e5a..2a442230806a455af7f149668d4a1985c2ebd921 100644
--- a/packages/frontend/src/scripts/aiscript/api.ts
+++ b/packages/frontend/src/scripts/aiscript/api.ts
@@ -1,6 +1,7 @@
 import { utils, values } from '@syuilo/aiscript';
 import * as os from '@/os';
 import { $i } from '@/account';
+import { miLocalStorage } from '@/local-storage';
 
 export function createAiScriptEnv(opts) {
 	let apiRequests = 0;
@@ -32,12 +33,12 @@ export function createAiScriptEnv(opts) {
 		}),
 		'Mk:save': values.FN_NATIVE(([key, value]) => {
 			utils.assertString(key);
-			localStorage.setItem('aiscript:' + opts.storageKey + ':' + key.value, JSON.stringify(utils.valToJs(value)));
+			miLocalStorage.setItem(`aiscript:${opts.storageKey}:${key.value}`, JSON.stringify(utils.valToJs(value)));
 			return values.NULL;
 		}),
 		'Mk:load': values.FN_NATIVE(([key]) => {
 			utils.assertString(key);
-			return utils.jsToVal(JSON.parse(localStorage.getItem('aiscript:' + opts.storageKey + ':' + key.value)));
+			return utils.jsToVal(JSON.parse(miLocalStorage.getItem(`aiscript:${opts.storageKey}:${key.value}`)));
 		}),
 	};
 }
diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts
index 7656770894b38aadbb3091452a59415f0841b2e2..1b723220ee460ef1306940f7b11ba49370b9af99 100644
--- a/packages/frontend/src/scripts/get-note-menu.ts
+++ b/packages/frontend/src/scripts/get-note-menu.ts
@@ -9,6 +9,7 @@ import copyToClipboard from '@/scripts/copy-to-clipboard';
 import { url } from '@/config';
 import { noteActions } from '@/store';
 import { notePage } from '@/filters/note';
+import { miLocalStorage } from '@/local-storage';
 
 export function getNoteMenu(props: {
 	note: misskey.entities.Note;
@@ -181,7 +182,7 @@ export function getNoteMenu(props: {
 		props.translating.value = true;
 		const res = await os.api('notes/translate', {
 			noteId: appearNote.id,
-			targetLang: localStorage.getItem('lang') || navigator.language,
+			targetLang: miLocalStorage.getItem('lang') || navigator.language,
 		});
 		props.translating.value = false;
 		props.translation.value = res;
diff --git a/packages/frontend/src/scripts/idb-proxy.ts b/packages/frontend/src/scripts/idb-proxy.ts
index 77bb84463cc2e59506dd99743194e1fa64c2c578..218682bb56b8fad69251fdb0f2324510a78257c9 100644
--- a/packages/frontend/src/scripts/idb-proxy.ts
+++ b/packages/frontend/src/scripts/idb-proxy.ts
@@ -22,15 +22,15 @@ if (idbAvailable) {
 
 export async function get(key: string) {
 	if (idbAvailable) return iget(key);
-	return JSON.parse(localStorage.getItem(fallbackName(key)));
+	return JSON.parse(window.localStorage.getItem(fallbackName(key)));
 }
 
 export async function set(key: string, val: any) {
 	if (idbAvailable) return iset(key, val);
-	return localStorage.setItem(fallbackName(key), JSON.stringify(val));
+	return window.localStorage.setItem(fallbackName(key), JSON.stringify(val));
 }
 
 export async function del(key: string) {
 	if (idbAvailable) return idel(key);
-	return localStorage.removeItem(fallbackName(key));
+	return window.localStorage.removeItem(fallbackName(key));
 }
diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/scripts/theme.ts
index 62a2b9459a7c4534296c4182c509477e5a0bf5a4..42cb00265dfda16fc6799c7ec695c3227af219e1 100644
--- a/packages/frontend/src/scripts/theme.ts
+++ b/packages/frontend/src/scripts/theme.ts
@@ -14,6 +14,7 @@ export type Theme = {
 import lightTheme from '@/themes/_light.json5';
 import darkTheme from '@/themes/_dark.json5';
 import { deepClone } from './clone';
+import { miLocalStorage } from '@/local-storage';
 
 export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X'));
 
@@ -84,8 +85,8 @@ export function applyTheme(theme: Theme, persist = true) {
 	document.documentElement.style.setProperty('color-schema', colorSchema);
 
 	if (persist) {
-		localStorage.setItem('theme', JSON.stringify(props));
-		localStorage.setItem('colorSchema', colorSchema);
+		miLocalStorage.setItem('theme', JSON.stringify(props));
+		miLocalStorage.setItem('colorSchema', colorSchema);
 	}
 
 	// 色計算など再度行えるようにクライアント全体に通知
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 4b1f47c2bcbb9df977e73862a0714abe1be2295d..97b6ebc188eee1fe092783f271216f0a07b187b2 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -86,6 +86,14 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'account',
 		default: [] as string[],
 	},
+	latestDonateDialogShowAt: {
+		where: 'account',
+		default: null,
+	},
+	neverShowDonateDialog: {
+		where: 'account',
+		default: false,
+	},
 
 	menu: {
 		where: 'deviceAccount',
@@ -274,7 +282,7 @@ export const defaultStore = markRaw(new Storage('base', {
 
 // TODO: 他のタブと永続化されたstateを同期
 
-const PREFIX = 'miux:';
+const PREFIX = 'miux:' as const;
 
 type Plugin = {
 	id: string;
@@ -296,6 +304,7 @@ interface Watcher {
 import lightTheme from '@/themes/l-light.json5';
 import darkTheme from '@/themes/d-green-lime.json5';
 import { Note, UserDetailed } from 'misskey-js/built/entities';
+import { miLocalStorage } from './local-storage';
 
 export class ColdDeviceStorage {
 	public static default = {
@@ -320,7 +329,7 @@ export class ColdDeviceStorage {
 		// TODO: indexedDBにする
 		//       ただしその際はnullチェックではなくキー存在チェックにしないとダメ
 		//       (indexedDBはnullを保存できるため、ユーザーが意図してnullを格納した可能性がある)
-		const value = localStorage.getItem(PREFIX + key);
+		const value = miLocalStorage.getItem(`${PREFIX}${key}`);
 		if (value == null) {
 			return ColdDeviceStorage.default[key];
 		} else {
@@ -330,14 +339,14 @@ export class ColdDeviceStorage {
 
 	public static set<T extends keyof typeof ColdDeviceStorage.default>(key: T, value: typeof ColdDeviceStorage.default[T]): void {
 		// 呼び出し側のバグ等で undefined が来ることがある
-		// undefined を文字列として localStorage に入れると参照する際の JSON.parse でコケて不具合の元になるため無視
+		// undefined を文字列として miLocalStorage に入れると参照する際の JSON.parse でコケて不具合の元になるため無視
 		// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 		if (value === undefined) {
 			console.error(`attempt to store undefined value for key '${key}'`);
 			return;
 		}
 
-		localStorage.setItem(PREFIX + key, JSON.stringify(value));
+		miLocalStorage.setItem(`${PREFIX}${key}`, JSON.stringify(value));
 
 		for (const watcher of this.watchers) {
 			if (watcher.key === key) watcher.callback(value);
diff --git a/packages/frontend/src/theme-store.ts b/packages/frontend/src/theme-store.ts
index fdc92ed793e5899a52b32093e5439813184284df..aa1244665b2713875de16467ee26e4f607f477e9 100644
--- a/packages/frontend/src/theme-store.ts
+++ b/packages/frontend/src/theme-store.ts
@@ -1,11 +1,13 @@
 import { api } from '@/os';
 import { $i } from '@/account';
 import { Theme } from './scripts/theme';
+import { miLocalStorage } from './local-storage';
 
-const lsCacheKey = $i ? `themes:${$i.id}` : '';
+const lsCacheKey = $i ? `themes:${$i.id}` as const : null;
 
 export function getThemes(): Theme[] {
-	return JSON.parse(localStorage.getItem(lsCacheKey) || '[]');
+	if ($i == null) return [];
+	return JSON.parse(miLocalStorage.getItem(lsCacheKey!) || '[]');
 }
 
 export async function fetchThemes(): Promise<void> {
@@ -13,7 +15,7 @@ export async function fetchThemes(): Promise<void> {
 
 	try {
 		const themes = await api('i/registry/get', { scope: ['client'], key: 'themes' });
-		localStorage.setItem(lsCacheKey, JSON.stringify(themes));
+		miLocalStorage.setItem(lsCacheKey!, JSON.stringify(themes));
 	} catch (err) {
 		if (err.code === 'NO_SUCH_KEY') return;
 		throw err;
@@ -21,14 +23,16 @@ export async function fetchThemes(): Promise<void> {
 }
 
 export async function addTheme(theme: Theme): Promise<void> {
+	if ($i == null) return;
 	await fetchThemes();
 	const themes = getThemes().concat(theme);
 	await api('i/registry/set', { scope: ['client'], key: 'themes', value: themes });
-	localStorage.setItem(lsCacheKey, JSON.stringify(themes));
+	miLocalStorage.setItem(lsCacheKey!, JSON.stringify(themes));
 }
 
 export async function removeTheme(theme: Theme): Promise<void> {
+	if ($i == null) return;
 	const themes = getThemes().filter(t => t.id !== theme.id);
 	await api('i/registry/set', { scope: ['client'], key: 'themes', value: themes });
-	localStorage.setItem(lsCacheKey, JSON.stringify(themes));
+	miLocalStorage.setItem(lsCacheKey!, JSON.stringify(themes));
 }
diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue
index 280e69e7dd39eb6e161502a752af42af66f79acb..f220501ee2d9b24055d4503330cbe59841562c05 100644
--- a/packages/frontend/src/ui/classic.vue
+++ b/packages/frontend/src/ui/classic.vue
@@ -51,6 +51,7 @@ import { mainRouter } from '@/router';
 import { PageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata';
 import { defaultStore } from '@/store';
 import { i18n } from '@/i18n';
+import { miLocalStorage } from '@/local-storage';
 const XHeaderMenu = defineAsyncComponent(() => import('./classic.header.vue'));
 const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
 
@@ -62,7 +63,7 @@ let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
 let widgetsShowing = $ref(false);
 let fullView = $ref(false);
 let globalHeaderHeight = $ref(0);
-const wallpaper = localStorage.getItem('wallpaper') != null;
+const wallpaper = miLocalStorage.getItem('wallpaper') != null;
 const showMenuOnTop = $computed(() => defaultStore.state.menuDisplay === 'top');
 let live2d = $shallowRef<HTMLIFrameElement>();
 let widgetsLeft = $ref();
@@ -123,7 +124,7 @@ function onAiClick(ev) {
 }
 
 if (window.innerWidth < 1024) {
-	localStorage.setItem('ui', 'default');
+	miLocalStorage.setItem('ui', 'default');
 	location.reload();
 }
 
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index 9e1fee5b6bb9f6843ea96dd7f35da1f5852906ea..06129ffc8702be19fb4e194f212651c26ac11bc1 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -71,6 +71,7 @@ import { Router } from '@/nirax';
 import { mainRouter } from '@/router';
 import { PageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata';
 import { deviceKind } from '@/scripts/device-kind';
+import { miLocalStorage } from '@/local-storage';
 const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
 const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue'));
 const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
@@ -170,7 +171,7 @@ function top() {
 	window.scroll({ top: 0, behavior: 'smooth' });
 }
 
-const wallpaper = localStorage.getItem('wallpaper') != null;
+const wallpaper = miLocalStorage.getItem('wallpaper') != null;
 </script>
 
 <style lang="scss" scoped>