diff --git a/src/client/app/common/views/components/user-lists-index.vue b/src/client/app/common/views/components/user-lists-index.vue
new file mode 100644
index 0000000000..0aec9658e8
--- /dev/null
+++ b/src/client/app/common/views/components/user-lists-index.vue
@@ -0,0 +1,82 @@
+<template>
+<div class="xkxvokkjlptzyewouewmceqcxhpgzprp">
+	<button class="ui" @click="add">{{ $t('create-list') }}</button>
+	<a v-for="list in lists" :key="list.id" @click="choice(list)">{{ list.title }}</a>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import XEditor from '../../../common/views/components/user-lists.vue';
+import i18n from '../../../i18n';
+
+export default Vue.extend({
+	i18n: i18n('common/views/components/user-lists.vue'),
+	data() {
+		return {
+			fetching: true,
+			lists: []
+		};
+	},
+	mounted() {
+		this.$root.api('users/lists/list').then(lists => {
+			this.fetching = false;
+			this.lists = lists;
+		});
+	},
+	methods: {
+		add() {
+			this.$root.dialog({
+				title: this.$t('list-name'),
+				input: true
+			}).then(async ({ canceled, result: title }) => {
+				if (canceled) return;
+				const list = await this.$root.api('users/lists/create', {
+					title
+				});
+
+				this.$emit('choosen', list);
+			});
+		},
+		choice(list) {
+			this.$emit('choosen', list);
+		},
+		close() {
+			(this as any).$refs.window.close();
+		}
+	}
+});
+</script>
+
+<style lang="stylus" scoped>
+.xkxvokkjlptzyewouewmceqcxhpgzprp
+	padding 16px
+
+	> button
+		display block
+		margin-bottom 16px
+		color var(--primaryForeground)
+		background var(--primary)
+		width 100%
+		border-radius 38px
+		user-select none
+		cursor pointer
+		padding 0 16px
+		min-width 100px
+		line-height 38px
+		font-size 14px
+		font-weight 700
+
+		&:hover
+			background var(--primaryLighten10)
+
+		&:active
+			background var(--primaryDarken10)
+
+	> a
+		display block
+		padding 16px
+		border solid 1px var(--faceDivider)
+		border-radius 4px
+
+</style>
diff --git a/src/client/app/common/views/components/user-lists.vue b/src/client/app/common/views/components/user-lists.vue
new file mode 100644
index 0000000000..b6803cd7f7
--- /dev/null
+++ b/src/client/app/common/views/components/user-lists.vue
@@ -0,0 +1,175 @@
+<template>
+<div class="vchtoekanapleubgzioubdtmlkribzfd">
+	<div v-if="game">
+		<x-gameroom :game="game" :self-nav="selfNav" @go-index="goIndex"/>
+	</div>
+	<div class="matching" v-else-if="matching">
+		<h1>{{ this.$t('matching.waiting-for').split('{}')[0] }}<b><mk-user-name :user="matching"/></b>{{ this.$t('matching.waiting-for').split('{}')[1] }}<mk-ellipsis/></h1>
+		<div class="cancel">
+			<form-button round @click="cancel">{{ $t('matching.cancel') }}</form-button>
+		</div>
+	</div>
+	<div v-else-if="gameId">
+		...
+	</div>
+	<div class="index" v-else>
+		<x-index @go="nav" @matching="onMatching"/>
+	</div>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import i18n from '../../../../../i18n';
+import XGameroom from './reversi.gameroom.vue';
+import XIndex from './reversi.index.vue';
+import Progress from '../../../../scripts/loading';
+
+export default Vue.extend({
+	i18n: i18n('common/views/components/games/reversi/reversi.vue'),
+	components: {
+		XGameroom,
+		XIndex
+	},
+
+	props: {
+		gameId: {
+			type: String,
+			required: false
+		},
+		selfNav: {
+			type: Boolean,
+			require: false,
+			default: true
+		}
+	},
+
+	data() {
+		return {
+			game: null,
+			matching: null,
+			connection: null,
+			pingClock: null
+		};
+	},
+
+	watch: {
+		game() {
+			this.$emit('gamed', this.game);
+		},
+
+		gameId() {
+			this.fetch();
+		}
+	},
+
+	mounted() {
+		this.fetch();
+
+		if (this.$store.getters.isSignedIn) {
+			this.connection = this.$root.stream.useSharedConnection('gamesReversi');
+
+			this.connection.on('matched', this.onMatched);
+
+			this.pingClock = setInterval(() => {
+				if (this.matching) {
+					this.connection.send('ping', {
+						id: this.matching.id
+					});
+				}
+			}, 3000);
+		}
+	},
+
+	beforeDestroy() {
+		if (this.connection) {
+			this.connection.dispose();
+			clearInterval(this.pingClock);
+		}
+	},
+
+	methods: {
+		fetch() {
+			if (this.gameId == null) {
+				this.game = null;
+			} else {
+				Progress.start();
+				this.$root.api('games/reversi/games/show', {
+					gameId: this.gameId
+				}).then(game => {
+					this.game = game;
+					Progress.done();
+				});
+			}
+		},
+
+		async nav(game, actualNav = true) {
+			if (this.selfNav) {
+				// 受け取ったゲーム情報が省略されたものなら完全な情報を取得する
+				if (game != null && (game.settings == null || game.settings.map == null)) {
+					game = await this.$root.api('games/reversi/games/show', {
+						gameId: game.id
+					});
+				}
+
+				this.game = game;
+			} else {
+				this.$emit('nav', game, actualNav);
+			}
+		},
+
+		onMatching(user) {
+			this.matching = user;
+		},
+
+		cancel() {
+			this.matching = null;
+			this.$root.api('games/reversi/match/cancel');
+		},
+
+		accept(invitation) {
+			this.$root.api('games/reversi/match', {
+				userId: invitation.parent.id
+			}).then(game => {
+				if (game) {
+					this.matching = null;
+
+					this.nav(game);
+				}
+			});
+		},
+
+		onMatched(game) {
+			this.matching = null;
+			this.game = game;
+			this.nav(game, false);
+		},
+
+		goIndex() {
+			this.nav(null);
+		}
+	}
+});
+</script>
+
+<style lang="stylus" scoped>
+.vchtoekanapleubgzioubdtmlkribzfd
+	color var(--text)
+	background var(--bg)
+
+	> .matching
+		> h1
+			margin 0
+			padding 24px
+			font-size 20px
+			text-align center
+			font-weight normal
+
+		> .cancel
+			margin 0 auto
+			padding 24px 0 0 0
+			max-width 200px
+			text-align center
+			border-top dashed 1px #c4cdd4
+
+</style>
diff --git a/src/client/app/desktop/views/components/user-lists-window.vue b/src/client/app/desktop/views/components/user-lists-window.vue
index 4f0af4a278..7ef04822a6 100644
--- a/src/client/app/desktop/views/components/user-lists-window.vue
+++ b/src/client/app/desktop/views/components/user-lists-window.vue
@@ -1,85 +1,31 @@
 <template>
 <mk-window ref="window" width="450px" height="500px" @closed="destroyDom">
 	<template #header><fa icon="list"/> {{ $t('title') }}</template>
-
-	<div class="xkxvokkjlptzyewouewmceqcxhpgzprp">
-		<button class="ui" @click="add">{{ $t('create-list') }}</button>
-		<a v-for="list in lists" :key="list.id" @click="choice(list)">{{ list.title }}</a>
-	</div>
+	<x-lists :class="$style.content" @listd="l => list = l">
 </mk-window>
 </template>
 
 <script lang="ts">
 import Vue from 'vue';
 import i18n from '../../../i18n';
+import { url } from '../../../config';
 
 export default Vue.extend({
-	i18n: i18n('desktop/views/components/user-lists-window.vue'),
+	i18n: i18n('desktop/views/components/game-window.vue'),
+	components: {
+		XLists: () => import('../../../common/views/components/user-lists.vue').then(m => m.default)
+	},
 	data() {
 		return {
-			fetching: true,
-			lists: []
+			list: null
 		};
-	},
-	mounted() {
-		this.$root.api('users/lists/list').then(lists => {
-			this.fetching = false;
-			this.lists = lists;
-		});
-	},
-	methods: {
-		add() {
-			this.$root.dialog({
-				title: this.$t('list-name'),
-				input: true
-			}).then(async ({ canceled, result: title }) => {
-				if (canceled) return;
-				const list = await this.$root.api('users/lists/create', {
-					title
-				});
-
-				this.$emit('choosen', list);
-			});
-		},
-		choice(list) {
-			this.$emit('choosen', list);
-		},
-		close() {
-			(this as any).$refs.window.close();
-		}
 	}
 });
 </script>
 
-<style lang="stylus" scoped>
-.xkxvokkjlptzyewouewmceqcxhpgzprp
-	padding 16px
-
-	> button
-		display block
-		margin-bottom 16px
-		color var(--primaryForeground)
-		background var(--primary)
-		width 100%
-		border-radius 38px
-		user-select none
-		cursor pointer
-		padding 0 16px
-		min-width 100px
-		line-height 38px
-		font-size 14px
-		font-weight 700
-
-		&:hover
-			background var(--primaryLighten10)
-
-		&:active
-			background var(--primaryDarken10)
-
-	> a
-		display block
-		padding 16px
-		border solid 1px var(--faceDivider)
-		border-radius 4px
+<style lang="stylus" module>
+.content
+	height 100%
+	overflow auto
 
 </style>