M9 S5 (frontend): shared BaseModal for the standard modals + BaseInput in Account
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 8s
CI & Build / Build & push image (push) Successful in 27s

- BaseModal.vue (new): the backdrop + dialog-panel shell (dimmed fixed overlay,
  bordered rounded panel, role=dialog, close on Escape + backdrop mousedown).
  Caller sizes/pads/shadows the panel via `panelClass`, picks start/center
  `align`, and sets an `ariaLabel` for header-less panels.
- LabelsModal, CommandPalette, and the AppShell keyboard-shortcuts overlay drop
  their hand-rolled backdrop+panel shells and slot their content into BaseModal
  (~12 lines of overlay boilerplate each → gone).
- NoteEditor deliberately keeps its own shell: its backdrop mousedown is
  drag-guarded and its Esc/⌘-Enter handling is bespoke (unsaved-edit safety),
  so folding it in would risk regressing the app's core editing surface (rule 28).
- AccountView's one device-name field now uses the shared BaseInput. SettingsView
  is intentionally NOT converted — its rows are a horizontal label+control pattern
  (checkbox/number/text, direct value mutation), a different shape than BaseInput's
  vertical form field.

Frontend-only; CI vue-tsc is the type/template gate (no local typecheck, rule 10).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
2026-07-23 21:58:59 -04:00
co-authored by Claude Opus 4.8
parent bdc5504419
commit 44a5466793
5 changed files with 66 additions and 47 deletions
+6 -11
View File
@@ -4,6 +4,7 @@ import { useRouter } from "vue-router";
import { useSessionStore } from "../stores/session";
import { useTitlesStore } from "../stores/titles";
import { useUiStore } from "../stores/ui";
import BaseModal from "./BaseModal.vue";
const emit = defineEmits<{ (e: "close"): void }>();
@@ -94,16 +95,11 @@ onMounted(async () => {
</script>
<template>
<div
class="fixed inset-0 z-50 flex items-start justify-center bg-black/40 p-4 pt-[12vh]"
@mousedown.self="emit('close')"
<BaseModal
panel-class="w-full max-w-lg overflow-hidden shadow-2xl"
aria-label="Command palette"
@close="emit('close')"
>
<div
class="w-full max-w-lg overflow-hidden rounded-xl border border-neutral-200 bg-white shadow-2xl dark:border-neutral-700 dark:bg-neutral-900"
role="dialog"
aria-modal="true"
aria-label="Command palette"
>
<input
ref="input"
v-model="query"
@@ -135,6 +131,5 @@ onMounted(async () => {
</li>
</ul>
<p v-else class="px-4 py-6 text-center text-sm text-neutral-400">No matches</p>
</div>
</div>
</BaseModal>
</template>