diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json
index 4f7c3b1..3a2c28d 100644
--- a/src-tauri/capabilities/default.json
+++ b/src-tauri/capabilities/default.json
@@ -9,6 +9,7 @@
     "core:default",
     "opener:default",
     "dialog:default",
+    "fs:allow-write-text-file",
     "fs:default"
   ]
-}
\ No newline at end of file
+}
diff --git a/src/App.vue b/src/App.vue
index 656184c..4018b86 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -36,4 +36,9 @@ main
 <style lang="sass">
 header
   margin-bottom: 1rem
+.ellipsis
+  display: inline-block
+  overflow: hidden
+  text-overflow: ellipsis
+  white-space: nowrap
 </style>
diff --git a/src/main.ts b/src/main.ts
index 8bc77e7..e28cd7b 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -4,6 +4,7 @@ import { definePreset } from '@primeuix/themes'
 import Aura from '@primeuix/themes/aura'
 import PrimeVue from 'primevue/config'
 import ToastService from 'primevue/toastservice'
+import Tooltip from 'primevue/tooltip'
 import 'primeicons/primeicons.css'
 
 import { router } from './router'
@@ -12,7 +13,6 @@ import App from './App.vue'
 const pinia = createPinia()
 const app = createApp(App)
 
-
 const themePreset = definePreset(Aura, {
   semantic: {
     primary: {
@@ -44,4 +44,5 @@ app
     },
   })
   .use(ToastService)
+  .directive('tooltip', Tooltip)
   .mount('#app')
diff --git a/src/store.ts b/src/store.ts
index f7f90f1..90754ad 100644
--- a/src/store.ts
+++ b/src/store.ts
@@ -17,12 +17,14 @@ export const useAppStore = defineStore('appStore', {
     } as InventoryState,
   }),
   getters: {
-    fileTree: (state) => fileTree(state.currentInventory),
-    fileDirectory: (state) => {
+    fileTree(state) {
+      return fileTree(state.currentInventory)
+    },
+    fileDirectory(state) {
       const split = fileTree(state.currentInventory)
       return state.currentInventory.filePath.replace(split[split.length - 1], '')
     },
-    fileName: (state) => {
+    fileName(state) {
       const split = fileTree(state.currentInventory)
       return split[split.length - 1]
     },
diff --git a/src/views/Editor/DataEditor.vue b/src/views/Editor/DataEditor.vue
index 1d46202..c80eb51 100644
--- a/src/views/Editor/DataEditor.vue
+++ b/src/views/Editor/DataEditor.vue
@@ -15,35 +15,40 @@ import {
 const props = defineProps<{
   columns: DataColumn[],
 }>()
-const model = defineModel<DataRow[]>()
+const emits = defineEmits<{
+  (e: 'dirty'): void
+}>()
+const model = defineModel<DataRow[]>({ required: true })
 const editingRows = ref([])
 
 const onRowEditSave = (event: { newData: any, index: number }) => {
   let { newData, index } = event
-  model.value![index] = newData
+  model.value[index] = newData
+  emits('dirty')
 }
 
 const onAddRow = (event: Event) => {
   event.preventDefault()
-  model.value!.push({ _id: uuidv7() })
-  console.log(model.value)
+  model.value.push({ _id: uuidv7() })
+  emits('dirty')
 
   requestAnimationFrame(() => {
-    (document.querySelector('tr:last-of-type button.edit') as HTMLButtonElement).click()
+    (document.querySelector('.data-editor tr:last-of-type button.edit') as HTMLButtonElement).click()
     requestAnimationFrame(() => {
-      (document.querySelector('tr:last-of-type input') as HTMLInputElement | undefined)?.focus()
+      (document.querySelector('.data-editor tr:last-of-type input') as HTMLInputElement | undefined)?.select()
     })
   })
 }
 
 const onDeleteRow = (event: Event, slotProps: { index: number }) => {
   event.preventDefault()
-  model.value!.splice(slotProps.index, 1)
+  model.value.splice(slotProps.index, 1)
+  emits('dirty')
 }
 </script>
 
 <template lang="pug">
-DataTable(
+DataTable.data-editor(
   @row-edit-save='onRowEditSave'
   v-model:editingRows='editingRows'
   :value='model'
@@ -93,6 +98,7 @@ DataTable(
         )
   Column(
     rowEditor
+    header='Actions'
     style='width:10%;min-width:8rem;'
     bodyStyle='text-align:center;'
   )
@@ -157,4 +163,7 @@ Button(
 :deep(.p-image)
   img
     max-width: 16rem
+:deep(td:last-of-type)
+  display: flex
+  padding-left: 8px
 </style>
diff --git a/src/views/Editor/Editor.vue b/src/views/Editor/Editor.vue
index 17659f2..784a3ca 100644
--- a/src/views/Editor/Editor.vue
+++ b/src/views/Editor/Editor.vue
@@ -1,5 +1,12 @@
 <script setup lang="ts">
-import { ref } from 'vue'
+import {
+  computed,
+  ref,
+} from 'vue'
+import { useRouter } from 'vue-router'
+import { useToast } from 'primevue'
+import { writeTextFile } from '@tauri-apps/plugin-fs'
+import Menubar from 'primevue/menubar'
 import Tabs from 'primevue/tabs'
 import TabList from 'primevue/tablist'
 import Tab from 'primevue/tab'
@@ -14,13 +21,71 @@ import DataEditor from './DataEditor.vue'
 import FieldEditor from './FieldEditor.vue'
 
 const appStore = useAppStore()
+const router = useRouter()
+const toast = useToast()
 
-const data = ref([...appStore.currentInventory.data?.rows || []])
-const fields = ref([...appStore.currentInventory.data?.columns || []])
+const data = ref(appStore.currentInventory.data?.rows)
+const fields = ref(appStore.currentInventory.data?.columns)
+const dirty = ref(false)
+const fileDirectory = ref(appStore.fileDirectory)
+const fileName = ref(appStore.fileName)
+
+const onSave = async () => {
+  try {
+    await writeTextFile(appStore.currentInventory.filePath, JSON.stringify({
+      rows: data.value,
+      columns: fields.value,
+    }, null, 2))
+  } catch (err) {
+    toast.add({
+      severity: 'error',
+      summary: 'The file could not be saved (permission issue?)',
+      detail: err,
+    })
+  }
+  dirty.value = false
+}
+
+const onQuit = () => {
+  // todo: should prompt to confirm
+  router.push('/')
+}
+
+const menu = computed(() => ([
+  {
+    label: 'Save',
+    icon: 'pi pi-save',
+    command: onSave,
+    disabled: !dirty.value,
+  },
+  {
+    label: 'Exit',
+    icon: 'pi pi-sign-out',
+    command: onQuit,
+  },
+]))
+
+const onDirty = () => {
+  dirty.value = true
+}
 </script>
 
 <template lang="pug">
-Tabs(value='data-editor')
+Menubar(
+  :model='menu'
+)
+  template(#end)
+    .context
+      span Editing for
+      span.ellipsis(
+        v-tooltip.bottom='fileDirectory'
+      )
+        span {{ fileDirectory }}
+      span.ellipsis(
+        v-tooltip.bottom='fileName'
+      )
+        span {{ fileName }}
+Tabs.tabs(value='data-editor')
   TabList
     Tab(value='data-editor') Data Editor
     Tab(value='field-editor') Field Editor
@@ -28,14 +93,22 @@ Tabs(value='data-editor')
     TabPanel(value='field-editor')
       FieldEditor(
         v-model='fields'
+        @dirty='onDirty'
       )
     TabPanel(value='data-editor')
       DataEditor(
         v-model='data'
         :columns='fields'
+        @dirty='onDirty'
       )
 </template>
 
 <style scoped lang="sass">
-
+.tabs
+  margin-top: 1rem
+.context
+  display: flex
+  gap: 1rem
+  .ellipsis
+    max-width: 8rem
 </style>
diff --git a/src/views/Editor/FieldEditor.vue b/src/views/Editor/FieldEditor.vue
index 6160d29..c0429cc 100644
--- a/src/views/Editor/FieldEditor.vue
+++ b/src/views/Editor/FieldEditor.vue
@@ -1,5 +1,6 @@
 <script setup lang="ts">
 import { ref } from 'vue'
+import { v1 as uuidv1 } from 'uuid'
 import Button from 'primevue/button'
 import DataTable from 'primevue/datatable'
 import Column from 'primevue/column'
@@ -11,18 +12,40 @@ import {
   type Column as DataColumn,
 } from 'src/types/data'
 
-const model = defineModel<DataColumn[]>()
+const emits = defineEmits<{
+  (e: 'dirty'): void
+}>()
+const model = defineModel<DataColumn[]>({ required: true })
 const editingFields = ref([])
 
 const onFieldEditSave = (event: { newData: any, index: number }) => {
   let { newData, index } = event
-  model.value![index] = newData
-  console.log(model.value)
+  model.value[index] = newData
+  emits('dirty')
+}
+
+const onAddField = (event: Event) => {
+  event.preventDefault()
+  model.value.push({ name: uuidv1().split('-')[0], type: 'text' })
+  emits('dirty')
+
+  requestAnimationFrame(() => {
+    (document.querySelector('.field-editor tr:last-of-type button.edit') as HTMLButtonElement).click()
+    requestAnimationFrame(() => {
+      (document.querySelector('.field-editor tr:last-of-type input') as HTMLInputElement | undefined)?.select()
+    })
+  })
+}
+
+const onDeleteField = (event: Event, slotProps: { index: number }) => {
+  event.preventDefault()
+  model.value.splice(slotProps.index, 1)
+  emits('dirty')
 }
 </script>
 
 <template lang="pug">
-DataTable(
+DataTable.field-editor(
   @row-edit-save='onFieldEditSave'
   v-model:editingRows='editingFields'
   :value='model'
@@ -61,13 +84,14 @@ DataTable(
       )
   Column(
     rowEditor
+    header='Actions'
     style='width:10%;min-width:8rem;'
     bodyStyle='text-align:center;'
   )
     template(
       #body='slotProps'
     )
-      Button(
+      Button.edit(
         icon='pi pi-pencil'
         aria-label='Row Edit'
         rounded
@@ -83,6 +107,9 @@ DataTable(
         rounded
         severity='danger'
         variant='text'
+        :onClick=`(e) => {
+          onDeleteField(e, slotProps)
+        }`
       )
     template(
       #editor='slotProps'
@@ -107,7 +134,16 @@ DataTable(
           slotProps.editorCancelCallback(e)
         }`
       )
+Button(
+  icon='pi pi-plus'
+  aria-label='Add New Field'
+  label='Add New Field'
+  @click='onAddField'
+)
 </template>
 
 <style scoped lang="sass">
+:deep(td:last-of-type)
+  display: flex
+  padding-left: 8px
 </style>
diff --git a/src/views/Home/Home.vue b/src/views/Home/Home.vue
index ee0248a..b2179d7 100644
--- a/src/views/Home/Home.vue
+++ b/src/views/Home/Home.vue
@@ -38,7 +38,7 @@ const onBrowse = async (e: Event) => {
 
 const onCreate = async (e: Event) => {
   e.preventDefault()
-  appStore.currentInventory.filePath = `${filePath}/${fileName}.json`
+  appStore.currentInventory.filePath = `${filePath.value}/${fileName.value}.json`
   appStore.currentInventory.data = {
     columns: [
       { name: 'name', type: 'text' },