Index: main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/App.vue
===================================================================
--- main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/App.vue	(revision 35431)
+++ main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/App.vue	(revision 35432)
@@ -17,5 +17,5 @@
         </div>
 
-        <div v-if="showInfo" class="card border-accent">
+        <div v-if="showInfo" class="paper">
             A tool to transcribe audio recordings of spoken MÄori. Basic support for editing the transcriptions is provided,
             and they can be downloaded in multiple formats.
@@ -81,4 +81,9 @@
 .transcription-list-item {
     transition: all 0.8s ease;
+    margin-bottom: 1em;
+
+    &:last-child {
+        margin-bottom: 0;
+    }
 }
 
Index: main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/components/AudioUpload.vue
===================================================================
--- main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/components/AudioUpload.vue	(revision 35431)
+++ main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/components/AudioUpload.vue	(revision 35432)
@@ -138,5 +138,5 @@
             this.isTranscribing = true;
 
-            await getTranscriptions(this.files, this.$store, this);
+            await this.getTranscriptions(this.files);
 
             this.files = []; // Clear the file list, as there is no reason the user would want to transcribe the same file multiple times over
@@ -152,47 +152,50 @@
         dismissFailure(id) {
             this.failures.delete(id);
+        },
+        /**
+         * Gets the transcription of each submitted audio file.
+         * @param {File[]} files The files to transcribe.
+         */
+        async getTranscriptions(files) {
+            await Util.delay(1000); // TODO: Remove - UI testing purposes only
+
+            try {
+                for await (const batch of transcribeService.batchTranscribeFiles(files)) {
+                    for (const t of batch) {
+                        if (!t.success) {
+                            createFailure(this, t);
+                        }
+                        else {
+                            const f = files.find(f => f.name === t.file_name);
+                            if (f === undefined) {
+                                createFailure(this, t, this.translations.get("ErrorTryAgain"));
+                            }
+
+                            const tvm = new TranscriptionViewModel(t, f);
+
+                            try {
+                                this.$store.commit("transcriptionAdd", tvm);
+                            }
+                            catch (TranscriptionExistsError) {
+                                createFailure(this, t, this.translations.get("AudioUpload_AlreadyTranscribed"));
+                            }
+                        }
+                    }
+                }
+            }
+            catch (e) {
+                console.error("Failed to transcribe files");
+                console.error(e);
+
+                const failure = new TranscriptionViewFailure(e.fileName, e.message);
+                this.failures.set(failure.id, failure);
+            }
+
+            function createFailure(that, t, reason = null) {
+                const failure = new TranscriptionViewFailure(t.file_name, reason ?? t.log);
+                that.failures.set(failure.id, failure);
+            }
         }
     }
 }
-
-/**
- * Gets the transcription of each submitted audio file.
- *
- * @param {File[]} files The files to transcribe.
- */
-async function getTranscriptions(files, store, vm) {
-    await Util.delay(200); // TODO: Remove - UI testing purposes only
-
-    try {
-        for await (const batch of transcribeService.batchTranscribeFiles(files)) {
-            for (const t of batch) {
-                if (!t.success) {
-                    const failure = new TranscriptionViewFailure(t.file_name, t.log);
-                    vm.failures.set(failure.id, failure);
-                }
-                else {
-                    const f = files.find(f => f.name === t.file_name);
-                    if (f === undefined) {
-                        throw new Error("File name mismatch");
-                    }
-
-                    // TODO: Hash file name and size instead. Good indicator that the user has uploaded a duplicate.
-                    const tvm = new TranscriptionViewModel(t, f);
-                    store.commit("transcriptionAdd", tvm);
-                    // let model = new TranscriptionViewModel(t, f);
-                    // model.words = getTranscriptionWords(t);
-
-                    // TranscriptionsListVM.transcriptions.set(model.id, model);
-                }
-            }
-        }
-    }
-    catch (e) {
-        console.error("Failed to transcribe files");
-        console.error(e);
-
-        const failure = new TranscriptionViewFailure(e.fileName, e.message);
-        vm.failures.set(failure.id, failure);
-    }
-}
 </script>
Index: main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/components/ToggleButton.vue
===================================================================
--- main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/components/ToggleButton.vue	(revision 35431)
+++ main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/components/ToggleButton.vue	(revision 35432)
@@ -10,5 +10,4 @@
     color: var(--primary-bg-color);
     background-color: var(--paper-color);
-    border: 2px solid var(--primary-bg-color);
     box-shadow: var(--primary-box-shadow-thin);
 }
Index: main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/main.js
===================================================================
--- main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/main.js	(revision 35431)
+++ main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/main.js	(revision 35432)
@@ -3,5 +3,4 @@
 import App from "./App.vue";
 import ToggleButton from "./components/ToggleButton.vue"
-import Util from "./js/Util"
 
 export class TranscriptionViewModel {
@@ -14,5 +13,5 @@
     constructor(transcription, file) {
         /** @type {String} The UUID of this transcription. */
-        this.id = Util.generateUuid();
+        this.id = file.name + file.size + file.type; // Should be a fairly reliable indicator of uniqueness.
 
         /** @type {String} The transcription. */
@@ -53,4 +52,10 @@
 }
 
+export class TranscriptionExistsError extends Error {
+    constructor(message = "", ...args) {
+        super(message, ...args);
+    }
+}
+
 const store = createStore({
     state() {
@@ -70,4 +75,7 @@
          */
         transcriptionAdd(state, transcription) {
+            if (state.rawTranscriptions.has(transcription.id)) {
+                throw new Error("Transcription already exists");
+            }
             state.rawTranscriptions.set(transcription.id, transcription);
         },
Index: main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/styles/_material.scss
===================================================================
--- main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/styles/_material.scss	(revision 35431)
+++ main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/styles/_material.scss	(revision 35432)
@@ -21,6 +21,7 @@
     --body-color: rgb(39, 39, 39);
     
-    --primary-box-shadow: 0px 2px 4px 0px #505050;
-    --primary-box-shadow-thin: 0px 1px 3px 0px #747474;
+    --primary-box-shadow: 0 2px 4px 0 #505050;
+    --primary-box-shadow-thin: 0 1px 3px 0 #747474;
+
     --hover-brightness: 92%;
 
@@ -50,8 +51,4 @@
     --primary-bg-color: var(--accent-bg-color);
     --primary-fg-color: var(--accent-fg-color);
-}
-
-.border-accent {
-    border: 2px var(--accent-bg-color) solid;
 }
 
@@ -248,8 +245,4 @@
     width: 100%; // Required for firefox
 
-    &:focus {
-        outline: none;
-    }
-
     &::-webkit-slider-thumb {
         -webkit-appearance: none;
