From 7b8725892de712d595cc4fbd262ceeed512ba4f2 Mon Sep 17 00:00:00 2001
From: Andrew Davis <1709934+Savid@users.noreply.github.com>
Date: Thu, 3 Apr 2025 16:01:22 +1000
Subject: [PATCH] split up macro routes

---
 ui/src/components/Combobox.tsx                |   47 +-
 ui/src/components/MacroBar.tsx                |    1 -
 ui/src/components/MacroForm.tsx               |  362 +++++
 ui/src/components/MacroStepCard.tsx           |  246 ++++
 ui/src/constants/macros.ts                    |    5 +
 ui/src/hooks/stores.ts                        |    8 +-
 ui/src/main.tsx                               |   34 +-
 .../devices.$id.settings.macros.add.tsx       |   63 +
 .../devices.$id.settings.macros.edit.tsx      |  108 ++
 ui/src/routes/devices.$id.settings.macros.tsx | 1181 ++---------------
 10 files changed, 959 insertions(+), 1096 deletions(-)
 create mode 100644 ui/src/components/MacroForm.tsx
 create mode 100644 ui/src/components/MacroStepCard.tsx
 create mode 100644 ui/src/constants/macros.ts
 create mode 100644 ui/src/routes/devices.$id.settings.macros.add.tsx
 create mode 100644 ui/src/routes/devices.$id.settings.macros.edit.tsx

diff --git a/ui/src/components/Combobox.tsx b/ui/src/components/Combobox.tsx
index f1342d4..383d88a 100644
--- a/ui/src/components/Combobox.tsx
+++ b/ui/src/components/Combobox.tsx
@@ -1,9 +1,7 @@
 import { useRef } from "react";
 import clsx from "clsx";
-
-import { Combobox as HeadlessCombobox, ComboboxProps as HeadlessComboboxProps, ComboboxInput, ComboboxOption, ComboboxOptions } from "@headlessui/react";
+import { Combobox as HeadlessCombobox, ComboboxInput, ComboboxOption, ComboboxOptions } from "@headlessui/react";
 import { cva } from "@/cva.config";
-
 import Card from "./Card";
 
 export interface ComboboxOption {
@@ -16,15 +14,15 @@ const sizes = {
   SM: "h-[32px] pl-3 pr-8 text-[13px]",
   MD: "h-[40px] pl-4 pr-10 text-sm",
   LG: "h-[48px] pl-4 pr-10 px-5 text-base",
-};
+} as const;
 
 const comboboxVariants = cva({
   variants: { size: sizes },
 });
 
-interface ComboboxProps<T> extends HeadlessComboboxProps<T, boolean, React.ExoticComponent<{
-    children?: React.ReactNode;
-}>> {
+type BaseProps = React.ComponentProps<typeof HeadlessCombobox>;
+
+interface ComboboxProps extends Omit<BaseProps, 'displayValue'> {
   displayValue: (option: ComboboxOption) => string;
   onInputChange: (option: string) => void;
   options: () => ComboboxOption[];
@@ -34,7 +32,7 @@ interface ComboboxProps<T> extends HeadlessComboboxProps<T, boolean, React.Exoti
   disabledMessage?: string;
 }
 
-export function Combobox<T>({
+export function Combobox({
   onInputChange,
   displayValue,
   options,
@@ -45,11 +43,11 @@ export function Combobox<T>({
   onChange,
   disabledMessage = "Input disabled",
   ...otherProps
-}: ComboboxProps<T>) {
+}: ComboboxProps) {
   const inputRef = useRef<HTMLInputElement>(null);
   const classes = comboboxVariants({ size });
 
-  const handleChange = (value: T) => {
+  const handleChange = (value: unknown) => {
     if (onChange) {
       onChange(value);
       inputRef.current?.blur();
@@ -57,14 +55,13 @@ export function Combobox<T>({
   };
 
   return (
-    <HeadlessCombobox<T, boolean, React.ExoticComponent<{ children?: React.ReactNode;}>> 
-      immediate 
+    <HeadlessCombobox 
       onChange={handleChange}
       {...otherProps}
     >
-    {() => (
+      {() => (
         <>
-        <Card className="w-auto !border border-solid !border-slate-800/30 shadow outline-0 dark:!border-slate-300/30">
+          <Card className="w-auto !border border-solid !border-slate-800/30 shadow outline-0 dark:!border-slate-300/30">
             <ComboboxInput
             ref={inputRef}
             className={clsx(
@@ -90,11 +87,11 @@ export function Combobox<T>({
             onChange={(event) => onInputChange(event.target.value)}
             disabled={disabled}
             />
-        </Card>
-        
-        {options().length > 0 && (
+          </Card>
+          
+          {options().length > 0 && (
             <ComboboxOptions className="absolute left-0 z-[100] mt-1 w-full max-h-60 overflow-auto rounded-md bg-white py-1 text-sm shadow-lg ring-1 ring-black/5 dark:bg-slate-800 dark:ring-slate-700 hide-scrollbar">
-            {options().map((option) => (
+              {options().map((option) => (
                 <ComboboxOption 
                 key={option.value} 
                 value={option}
@@ -109,21 +106,21 @@ export function Combobox<T>({
                   "dark:text-slate-300 dark:hover:bg-slate-700 dark:ui-active:bg-slate-700 dark:ui-active:text-blue-200"
                 )}
                 >
-                {option.label}
+                  {option.label}
                 </ComboboxOption>
-            ))}
+              ))}
             </ComboboxOptions>
-        )}
-        
-        {options().length === 0 && inputRef.current?.value && (
+          )}
+          
+          {options().length === 0 && inputRef.current?.value && (
             <div className="absolute left-0 z-[100] mt-1 w-full rounded-md bg-white dark:bg-slate-800 py-2 px-4 text-sm shadow-lg ring-1 ring-black/5 dark:ring-slate-700">
               <div className="text-slate-500 dark:text-slate-400">
                 {emptyMessage}
               </div>
             </div>
-        )}
+          )}
         </>
-    )}
+      )}
     </HeadlessCombobox>
   );
 }
\ No newline at end of file
diff --git a/ui/src/components/MacroBar.tsx b/ui/src/components/MacroBar.tsx
index a0a7275..107d8de 100644
--- a/ui/src/components/MacroBar.tsx
+++ b/ui/src/components/MacroBar.tsx
@@ -12,7 +12,6 @@ export default function MacroBar() {
   const { executeMacro } = useKeyboard();
   const [send] = useJsonRpc();
 
-  // Set up sendFn and initialize macros if needed
   useEffect(() => {
     setSendFn(send);
     
diff --git a/ui/src/components/MacroForm.tsx b/ui/src/components/MacroForm.tsx
new file mode 100644
index 0000000..c482f7b
--- /dev/null
+++ b/ui/src/components/MacroForm.tsx
@@ -0,0 +1,362 @@
+import { useState } from "react";
+
+import { LuPlus, LuInfo } from "react-icons/lu";
+
+import { KeySequence } from "@/hooks/stores";
+import { Button } from "@/components/Button";
+import { InputFieldWithLabel, FieldError } from "@/components/InputField";
+import Fieldset from "@/components/Fieldset";
+import { MacroStepCard } from "@/components/MacroStepCard";
+import Modal from "@/components/Modal";
+import { DEFAULT_DELAY, MAX_STEPS_PER_MACRO, MAX_KEYS_PER_STEP } from "@/constants/macros";
+
+interface ValidationErrors {
+  name?: string;
+  steps?: Record<number, {
+    keys?: string;
+    modifiers?: string;
+    delay?: string;
+  }>;
+}
+
+interface MacroFormProps {
+  initialData: Partial<KeySequence>;
+  onSubmit: (macro: Partial<KeySequence>) => Promise<void>;
+  onCancel: () => void;
+  isSubmitting?: boolean;
+  submitText?: string;
+  showCancelConfirm?: boolean;
+  onCancelConfirm?: () => void;
+  showDelete?: boolean;
+  onDelete?: () => void;
+  isDeleting?: boolean;
+}
+
+export function MacroForm({
+  initialData,
+  onSubmit,
+  onCancel,
+  isSubmitting = false,
+  submitText = "Save Macro",
+  showCancelConfirm = false,
+  onCancelConfirm,
+  showDelete = false,
+  onDelete,
+  isDeleting = false
+}: MacroFormProps) {
+  const [macro, setMacro] = useState<Partial<KeySequence>>(initialData);
+  const [keyQueries, setKeyQueries] = useState<Record<number, string>>({});
+  const [errors, setErrors] = useState<ValidationErrors>({});
+  const [errorMessage, setErrorMessage] = useState<string | null>(null);
+  const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
+
+  const showTemporaryError = (message: string) => {
+    setErrorMessage(message);
+    setTimeout(() => setErrorMessage(null), 3000);
+  };
+
+  const validateForm = (): boolean => {
+    const newErrors: ValidationErrors = {};
+
+    // Name validation
+    if (!macro.name?.trim()) {
+      newErrors.name = "Name is required";
+    } else if (macro.name.trim().length > 50) {
+      newErrors.name = "Name must be less than 50 characters";
+    }
+  
+    if (!macro.steps?.length) {
+      newErrors.steps = { 0: { keys: "At least one step is required" } };
+    } else {
+      const hasKeyOrModifier = macro.steps.some(step => 
+        (step.keys?.length || 0) > 0 || (step.modifiers?.length || 0) > 0
+      );
+
+      if (!hasKeyOrModifier) {
+        newErrors.steps = { 0: { keys: "At least one step must have keys or modifiers" } };
+      }
+    }
+
+    setErrors(newErrors);
+    return Object.keys(newErrors).length === 0;
+  };
+
+  const handleSubmit = async () => {
+    if (!validateForm()) {
+      showTemporaryError("Please fix the validation errors");
+      return;
+    }
+
+    try {
+      await onSubmit(macro);
+    } catch (error) {
+      if (error instanceof Error) {
+        showTemporaryError(error.message);
+      } else {
+        showTemporaryError("An error occurred while saving");
+      }
+    }
+  };
+
+  const handleKeySelect = (stepIndex: number, option: { value: string | null; keys?: string[] }) => {
+    const newSteps = [...(macro.steps || [])];
+    if (!newSteps[stepIndex]) return;
+
+    if (option.keys) {
+      newSteps[stepIndex].keys = option.keys;
+    } else if (option.value) {
+      if (!newSteps[stepIndex].keys) {
+        newSteps[stepIndex].keys = [];
+      }
+      const keysArray = Array.isArray(newSteps[stepIndex].keys) ? newSteps[stepIndex].keys : [];
+      if (keysArray.length >= MAX_KEYS_PER_STEP) {
+        showTemporaryError(`Maximum of ${MAX_KEYS_PER_STEP} keys per step allowed`);
+        return;
+      }
+      newSteps[stepIndex].keys = [...keysArray, option.value];
+    }
+    setMacro({ ...macro, steps: newSteps });
+    
+    if (errors.steps?.[stepIndex]?.keys) {
+      const newErrors = { ...errors };
+      delete newErrors.steps?.[stepIndex].keys;
+      if (Object.keys(newErrors.steps?.[stepIndex] || {}).length === 0) {
+        delete newErrors.steps?.[stepIndex];
+      }
+      if (Object.keys(newErrors.steps || {}).length === 0) {
+        delete newErrors.steps;
+      }
+      setErrors(newErrors);
+    }
+  };
+
+  const handleKeyQueryChange = (stepIndex: number, query: string) => {
+    setKeyQueries(prev => ({ ...prev, [stepIndex]: query }));
+  };
+
+  const handleModifierChange = (stepIndex: number, modifiers: string[]) => {
+    const newSteps = [...(macro.steps || [])];
+    newSteps[stepIndex].modifiers = modifiers;
+    setMacro({ ...macro, steps: newSteps });
+    
+    // Clear step errors when modifiers are added
+    if (errors.steps?.[stepIndex]?.keys && modifiers.length > 0) {
+      const newErrors = { ...errors };
+      delete newErrors.steps?.[stepIndex].keys;
+      if (Object.keys(newErrors.steps?.[stepIndex] || {}).length === 0) {
+        delete newErrors.steps?.[stepIndex];
+      }
+      if (Object.keys(newErrors.steps || {}).length === 0) {
+        delete newErrors.steps;
+      }
+      setErrors(newErrors);
+    }
+  };
+
+  const handleDelayChange = (stepIndex: number, delay: number) => {
+    const newSteps = [...(macro.steps || [])];
+    newSteps[stepIndex].delay = delay;
+    setMacro({ ...macro, steps: newSteps });
+  };
+
+  const handleStepMove = (stepIndex: number, direction: 'up' | 'down') => {
+    const newSteps = [...(macro.steps || [])];
+    const newIndex = direction === 'up' ? stepIndex - 1 : stepIndex + 1;
+    [newSteps[stepIndex], newSteps[newIndex]] = [newSteps[newIndex], newSteps[stepIndex]];
+    setMacro({ ...macro, steps: newSteps });
+  };
+
+  const isMaxStepsReached = (macro.steps?.length || 0) >= MAX_STEPS_PER_MACRO;
+
+  return (
+    <>
+      <div className="space-y-4">
+        <Fieldset>
+          <InputFieldWithLabel
+            type="text"
+            label="Macro Name"
+            placeholder="Macro Name"
+            value={macro.name}
+            error={errors.name}
+            onChange={e => {
+              setMacro(prev => ({ ...prev, name: e.target.value }));
+              if (errors.name) {
+                const newErrors = { ...errors };
+                delete newErrors.name;
+                setErrors(newErrors);
+              }
+            }}
+          />
+        </Fieldset>
+
+        <div>
+          <div className="flex items-center justify-between text-sm">
+            <div className="flex items-center gap-1">
+              <label className="font-medium text-slate-700 dark:text-slate-200">
+                Steps
+              </label>
+              <div className="group relative cursor-pointer">
+                <LuInfo className="h-4 w-4 text-slate-400 hover:text-slate-600 dark:text-slate-500 dark:hover:text-slate-400" />
+                <div className="absolute left-1/2 top-full z-10 mt-1 hidden w-64 -translate-x-1/2 rounded-md bg-slate-800 px-3 py-2 text-xs text-white shadow-lg group-hover:block dark:bg-slate-700">
+                  <p>Each step is a collection of keys and/or modifiers that will be executed in order. You can add up to a maximum of {MAX_STEPS_PER_MACRO} steps per macro.</p>
+                </div>
+              </div>
+            </div>
+            <span className="text-slate-500 dark:text-slate-400">
+              {macro.steps?.length || 0}/{MAX_STEPS_PER_MACRO} steps
+            </span>
+          </div>
+          {errors.steps && errors.steps[0]?.keys && (
+            <div className="mt-2">
+              <FieldError error={errors.steps[0].keys} />
+            </div>
+          )}
+          <Fieldset>
+            <div className="mt-2 space-y-4">
+              {(macro.steps || []).map((step, stepIndex) => (
+                <MacroStepCard
+                  key={stepIndex}
+                  step={step}
+                  stepIndex={stepIndex}
+                  onDelete={macro.steps && macro.steps.length > 1 ? () => {
+                    const newSteps = [...(macro.steps || [])];
+                    newSteps.splice(stepIndex, 1);
+                    setMacro(prev => ({ ...prev, steps: newSteps }));
+                  } : undefined}
+                  onMoveUp={() => handleStepMove(stepIndex, 'up')}
+                  onMoveDown={() => handleStepMove(stepIndex, 'down')}
+                  onKeySelect={(option) => handleKeySelect(stepIndex, option)}
+                  onKeyQueryChange={(query) => handleKeyQueryChange(stepIndex, query)}
+                  keyQuery={keyQueries[stepIndex] || ''}
+                  onModifierChange={(modifiers) => handleModifierChange(stepIndex, modifiers)}
+                  onDelayChange={(delay) => handleDelayChange(stepIndex, delay)}
+                  isLastStep={stepIndex === (macro.steps?.length || 0) - 1}
+                />
+              ))}
+            </div>
+          </Fieldset>
+
+          <div className="mt-4">
+            <Button
+              size="MD"
+              theme="light"
+              fullWidth
+              LeadingIcon={LuPlus}
+              text={`Add Step ${isMaxStepsReached ? `(${MAX_STEPS_PER_MACRO} max)` : ''}`}
+              onClick={() => {
+                if (isMaxStepsReached) {
+                  showTemporaryError(`You can only add a maximum of ${MAX_STEPS_PER_MACRO} steps per macro.`);
+                  return;
+                }
+                
+                setMacro(prev => ({
+                  ...prev,
+                  steps: [
+                    ...(prev.steps || []), 
+                    { keys: [], modifiers: [], delay: DEFAULT_DELAY }
+                  ],
+                }));
+                setErrors({});
+              }}
+              disabled={isMaxStepsReached}
+            />
+          </div>
+
+          {errorMessage && (
+            <div className="mt-4">
+              <FieldError error={errorMessage} />
+            </div>
+          )}
+
+          <div className="mt-6 flex items-center justify-between">
+            {showCancelConfirm ? (
+              <div className="flex items-center gap-2">
+                <span className="text-sm text-slate-600 dark:text-slate-400">
+                  Cancel changes?
+                </span>
+                <Button
+                  size="SM"
+                  theme="danger"
+                  text="Yes"
+                  onClick={onCancelConfirm}
+                />
+                <Button
+                  size="SM"
+                  theme="light"
+                  text="No"
+                  onClick={() => onCancel()}
+                />
+              </div>
+            ) : (
+              <>
+                <div className="flex gap-x-2">
+                  <Button
+                    size="SM"
+                    theme="primary"
+                    text={isSubmitting ? "Saving..." : submitText}
+                    onClick={handleSubmit}
+                    disabled={isSubmitting}
+                  />
+                  <Button
+                    size="SM"
+                    theme="light"
+                    text="Cancel"
+                    onClick={onCancel}
+                  />
+                </div>
+                {showDelete && (
+                  <Button
+                    size="SM"
+                    theme="danger"
+                    text={isDeleting ? "Deleting..." : "Delete Macro"}
+                    onClick={() => setShowDeleteConfirm(true)}
+                    disabled={isDeleting}
+                  />
+                )}
+              </>
+            )}
+          </div>
+        </div>
+      </div>
+
+      <Modal
+        open={showDeleteConfirm}
+        onClose={() => setShowDeleteConfirm(false)}
+      >
+        <div className="mx-auto max-w-xl px-4 transition-all duration-300 ease-in-out">
+          <div className="relative w-full overflow-hidden rounded-lg bg-white p-6 text-left align-middle shadow-xl transition-all dark:bg-slate-800 pointer-events-auto">
+            <div className="space-y-4">
+              <div className="space-y-0">
+                <h2 className="text-lg font-bold leading-tight text-black dark:text-white">
+                  Delete Macro
+                </h2>
+                <div className="text-sm leading-snug text-slate-600 dark:text-slate-400">
+                  Are you sure you want to delete this macro? This action cannot be undone.
+                </div>
+              </div>
+
+              <div className="flex justify-end gap-x-2">
+                <Button
+                  size="SM"
+                  theme="danger"
+                  text={isDeleting ? "Deleting..." : "Delete"}
+                  onClick={() => {
+                    onDelete?.();
+                    setShowDeleteConfirm(false);
+                  }}
+                  disabled={isDeleting}
+                />
+                <Button
+                  size="SM"
+                  theme="light"
+                  text="Cancel"
+                  onClick={() => setShowDeleteConfirm(false)}
+                />
+              </div>
+            </div>
+          </div>
+        </div>
+      </Modal>
+    </>
+  );
+} 
\ No newline at end of file
diff --git a/ui/src/components/MacroStepCard.tsx b/ui/src/components/MacroStepCard.tsx
new file mode 100644
index 0000000..1bd6329
--- /dev/null
+++ b/ui/src/components/MacroStepCard.tsx
@@ -0,0 +1,246 @@
+import { LuArrowUp, LuArrowDown, LuX, LuInfo } from "react-icons/lu";
+
+import { Button } from "@/components/Button";
+import { Combobox } from "@/components/Combobox";
+import { SelectMenuBasic } from "@/components/SelectMenuBasic";
+import Card from "@/components/Card";
+import { keys, modifiers, keyDisplayMap } from "@/keyboardMappings";
+import { MAX_KEYS_PER_STEP } from "@/constants/macros";
+
+// Filter out modifier keys since they're handled in the modifiers section
+const modifierKeyPrefixes = ['Alt', 'Control', 'Shift', 'Meta'];
+
+const keyOptions = Object.keys(keys)
+  .filter(key => !modifierKeyPrefixes.some(prefix => key.startsWith(prefix)))
+  .map(key => ({
+    value: key,
+    label: keyDisplayMap[key] || key,
+  }));
+
+const modifierOptions = Object.keys(modifiers).map(modifier => ({
+  value: modifier,
+  label: modifier.replace(/^(Control|Alt|Shift|Meta)(Left|Right)$/, "$1 $2"),
+}));
+
+const groupedModifiers: Record<string, typeof modifierOptions> = {
+  Control: modifierOptions.filter(mod => mod.value.startsWith('Control')),
+  Shift: modifierOptions.filter(mod => mod.value.startsWith('Shift')),
+  Alt: modifierOptions.filter(mod => mod.value.startsWith('Alt')),
+  Meta: modifierOptions.filter(mod => mod.value.startsWith('Meta')),
+};
+
+const PRESET_DELAYS = [
+  { value: "50", label: "50ms" },
+  { value: "100", label: "100ms" },
+  { value: "200", label: "200ms" },
+  { value: "300", label: "300ms" },
+  { value: "500", label: "500ms" },
+  { value: "750", label: "750ms" },
+  { value: "1000", label: "1000ms" },
+  { value: "1500", label: "1500ms" },
+  { value: "2000", label: "2000ms" },
+];
+
+interface MacroStep {
+  keys: string[];
+  modifiers: string[];
+  delay: number;
+}
+
+interface MacroStepCardProps {
+  step: MacroStep;
+  stepIndex: number;
+  onDelete?: () => void;
+  onMoveUp?: () => void;
+  onMoveDown?: () => void;
+  onKeySelect: (option: { value: string | null; keys?: string[] }) => void;
+  onKeyQueryChange: (query: string) => void;
+  keyQuery: string;
+  onModifierChange: (modifiers: string[]) => void;
+  onDelayChange: (delay: number) => void;
+  isLastStep: boolean;
+}
+
+const ensureArray = <T,>(arr: T[] | null | undefined): T[] => {
+  return Array.isArray(arr) ? arr : [];
+};
+
+export function MacroStepCard({
+  step,
+  stepIndex,
+  onDelete,
+  onMoveUp,
+  onMoveDown,
+  onKeySelect,
+  onKeyQueryChange,
+  keyQuery,
+  onModifierChange,
+  onDelayChange,
+  isLastStep
+}: MacroStepCardProps) {
+  const getFilteredKeys = () => {
+    const selectedKeys = ensureArray(step.keys);
+    const availableKeys = keyOptions.filter(option => !selectedKeys.includes(option.value));
+    
+    if (keyQuery === '') {
+      return availableKeys;
+    } else {
+      return availableKeys.filter(option => option.label.toLowerCase().includes(keyQuery.toLowerCase()));
+    }
+  };
+
+  return (
+    <Card className="p-4">
+      <div className="mb-2 flex items-center justify-between">
+        <div className="flex items-center gap-1.5">
+          <div className="flex items-center gap-1">
+            <Button
+              size="XS"
+              theme="light"
+              onClick={onMoveUp}
+              disabled={stepIndex === 0}
+              LeadingIcon={LuArrowUp}
+            />
+            <Button
+              size="XS"
+              theme="light"
+              onClick={onMoveDown}
+              disabled={isLastStep}
+              LeadingIcon={LuArrowDown}
+            />
+          </div>
+          <span className="flex h-5 w-5 items-center justify-center rounded-full bg-blue-100 text-xs font-semibold text-blue-700 dark:bg-blue-900/40 dark:text-blue-200">
+            {stepIndex + 1}
+          </span>
+        </div>
+        
+        <div className="flex items-center space-x-2">
+          {onDelete && (
+            <Button
+              size="XS"
+              theme="danger"
+              text="Delete"
+              onClick={onDelete}
+            />
+          )}
+        </div>
+      </div>
+      
+      <div className="space-y-4 mt-2">
+        <div className="w-full flex flex-col gap-2">
+          <label className="text-sm font-medium text-slate-700 dark:text-slate-300">
+            Modifiers
+          </label>
+          <div className="inline-flex flex-wrap gap-3">
+            {Object.entries(groupedModifiers).map(([group, mods]) => (
+              <div key={group} className="relative min-w-[120px] rounded-md border border-slate-200 dark:border-slate-700 p-2">
+                <span className="absolute -top-2.5 left-2 px-1 text-xs font-medium bg-white dark:bg-slate-800 text-slate-500 dark:text-slate-400">
+                  {group}
+                </span>
+                <div className="flex flex-wrap gap-1">
+                  {mods.map(option => (
+                    <label 
+                      key={option.value} 
+                      className={`flex items-center px-2 py-1 rounded border cursor-pointer text-xs font-medium transition-colors ${
+                        ensureArray(step.modifiers).includes(option.value) 
+                          ? 'bg-blue-100 border-blue-300 text-blue-700 dark:bg-blue-900/40 dark:border-blue-600 dark:text-blue-200' 
+                          : 'bg-slate-100 border-slate-200 text-slate-600 hover:bg-slate-200 dark:bg-slate-800 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-700'
+                      }`}
+                    >
+                      <input
+                        type="checkbox"
+                        className="sr-only"
+                        checked={ensureArray(step.modifiers).includes(option.value)}
+                        onChange={e => {
+                          const modifiersArray = ensureArray(step.modifiers);
+                          const newModifiers = e.target.checked
+                            ? [...modifiersArray, option.value]
+                            : modifiersArray.filter(m => m !== option.value);
+                          onModifierChange(newModifiers);
+                        }}
+                      />
+                      {option.label.split(' ')[1] || option.label}
+                    </label>
+                  ))}
+                </div>
+              </div>
+            ))}
+          </div>
+        </div>
+        
+        <div className="w-full flex flex-col gap-1">
+          <div className="flex items-center gap-1">
+            <label className="text-sm font-medium text-slate-700 dark:text-slate-300">
+              Keys
+            </label>
+            <div className="group relative cursor-pointer">
+              <LuInfo className="h-4 w-4 text-slate-400 hover:text-slate-600 dark:text-slate-500 dark:hover:text-slate-400" />
+              <div className="absolute left-1/2 top-full z-10 mt-1 hidden w-64 -translate-x-1/2 rounded-md bg-slate-800 px-3 py-2 text-xs text-white shadow-lg group-hover:block dark:bg-slate-700">
+                <p>You can add up to a maximum of {MAX_KEYS_PER_STEP} keys to press per step.</p>
+              </div>
+            </div>
+          </div>
+          <div className="flex flex-wrap gap-1 pb-2">
+            {ensureArray(step.keys).map((key, keyIndex) => (
+              <span
+                key={keyIndex}
+                className="inline-flex items-center rounded-md bg-blue-100 px-1 text-xs font-medium text-blue-800 dark:bg-blue-900/40 dark:text-blue-200"
+              >
+                <span className="px-1">
+                  {keyDisplayMap[key] || key}
+                </span>
+                <Button
+                  size="XS"
+                  className=""
+                  theme="blank"
+                  onClick={() => {
+                    const newKeys = ensureArray(step.keys).filter((_, i) => i !== keyIndex);
+                    onKeySelect({ value: null, keys: newKeys });
+                  }}
+                  LeadingIcon={LuX}
+                />
+              </span>
+            ))}
+          </div>
+          <div className="relative w-full">
+            <Combobox
+              onChange={(value: { value: string; label: string }) => onKeySelect(value)}
+              displayValue={() => keyQuery}
+              onInputChange={onKeyQueryChange}
+              options={getFilteredKeys}
+              disabledMessage="Max keys reached"
+              size="SM"
+              immediate
+              disabled={ensureArray(step.keys).length >= MAX_KEYS_PER_STEP}
+              placeholder={ensureArray(step.keys).length >= MAX_KEYS_PER_STEP ? "Max keys reached" : "Search for key..."}
+              emptyMessage="No matching keys found"
+            />
+          </div>
+        </div>
+        
+        <div className="w-full flex flex-col gap-1">
+          <div className="flex items-center gap-1">
+            <label className="text-sm font-medium text-slate-700 dark:text-slate-300">
+              Step Duration
+            </label>
+            <div className="group relative cursor-pointer">
+              <LuInfo className="h-4 w-4 text-slate-400 hover:text-slate-600 dark:text-slate-500 dark:hover:text-slate-400" />
+              <div className="absolute left-1/2 top-full z-10 mt-1 hidden w-64 -translate-x-1/2 rounded-md bg-slate-800 px-3 py-2 text-xs text-white shadow-lg group-hover:block dark:bg-slate-700">
+                <p>The time to wait after pressing the keys in this step before moving to the next step. This helps ensure reliable key presses when automating keyboard input.</p>
+              </div>
+            </div>
+          </div>
+          <div className="flex items-center gap-3">
+            <SelectMenuBasic
+              size="SM"
+              fullWidth
+              value={step.delay.toString()}
+              onChange={(e) => onDelayChange(parseInt(e.target.value, 10))}
+              options={PRESET_DELAYS}
+            />
+          </div>
+        </div>
+      </div>
+    </Card>
+  );
+} 
\ No newline at end of file
diff --git a/ui/src/constants/macros.ts b/ui/src/constants/macros.ts
new file mode 100644
index 0000000..853cfe9
--- /dev/null
+++ b/ui/src/constants/macros.ts
@@ -0,0 +1,5 @@
+export const DEFAULT_DELAY = 50;
+export const MAX_STEPS_PER_MACRO = 10;
+export const MAX_KEYS_PER_STEP = 10;
+export const MAX_TOTAL_MACROS = 25; 
+export const COPY_SUFFIX = "(copy)";
\ No newline at end of file
diff --git a/ui/src/hooks/stores.ts b/ui/src/hooks/stores.ts
index 05bfdf0..0fa4121 100644
--- a/ui/src/hooks/stores.ts
+++ b/ui/src/hooks/stores.ts
@@ -1,5 +1,6 @@
 import { create } from "zustand";
 import { createJSONStorage, persist } from "zustand/middleware";
+import { MAX_STEPS_PER_MACRO, MAX_TOTAL_MACROS, MAX_KEYS_PER_STEP } from "@/constants/macros";
 
 // Define the JsonRpc types for better type checking
 interface JsonRpcResponse {
@@ -671,7 +672,6 @@ export interface KeySequenceStep {
 export interface KeySequence {
   id: string;
   name: string;
-  description?: string;
   steps: KeySequenceStep[];
   sortOrder?: number;
 }
@@ -686,9 +686,9 @@ export interface MacrosState {
   setSendFn: (sendFn: ((method: string, params: unknown, callback?: ((resp: JsonRpcResponse) => void) | undefined) => void)) => void;
 }
 
-const MAX_STEPS_PER_MACRO = 10;
-const MAX_TOTAL_MACROS = 25;
-const MAX_KEYS_PER_STEP = 10;
+export const generateMacroId = () => {
+  return Math.random().toString(36).substring(2, 9);
+};
 
 export const useMacrosStore = create<MacrosState>((set, get) => ({
   macros: [],
diff --git a/ui/src/main.tsx b/ui/src/main.tsx
index 4b29129..e09a2a9 100644
--- a/ui/src/main.tsx
+++ b/ui/src/main.tsx
@@ -40,10 +40,12 @@ import * as SettingsAccessIndexRoute from "./routes/devices.$id.settings.access.
 import SettingsHardwareRoute from "./routes/devices.$id.settings.hardware";
 import SettingsVideoRoute from "./routes/devices.$id.settings.video";
 import SettingsAppearanceRoute from "./routes/devices.$id.settings.appearance";
-import SettingsMacrosRoute from "./routes/devices.$id.settings.macros";
 import * as SettingsGeneralIndexRoute from "./routes/devices.$id.settings.general._index";
 import SettingsGeneralUpdateRoute from "./routes/devices.$id.settings.general.update";
 import SecurityAccessLocalAuthRoute from "./routes/devices.$id.settings.access.local-auth";
+import SettingsMacrosRoute from "./routes/devices.$id.settings.macros";
+import SettingsMacrosAddRoute from "./routes/devices.$id.settings.macros.add";
+import SettingsMacrosEditRoute from "./routes/devices.$id.settings.macros.edit";
 
 export const isOnDevice = import.meta.env.MODE === "device";
 export const isInCloud = !isOnDevice;
@@ -178,7 +180,20 @@ if (isOnDevice) {
             },
             {
               path: "macros",
-              element: <SettingsMacrosRoute />,
+              children: [
+                {
+                  index: true,
+                  element: <SettingsMacrosRoute />,
+                },
+                {
+                  path: "add",
+                  element: <SettingsMacrosAddRoute />,
+                },
+                {
+                  path: ":macroId/edit",
+                  element: <SettingsMacrosEditRoute />,
+                },
+              ],
             },
           ],
         },
@@ -290,7 +305,20 @@ if (isOnDevice) {
                     },
                     {
                       path: "macros",
-                      element: <SettingsMacrosRoute />,
+                      children: [
+                        {
+                          index: true,
+                          element: <SettingsMacrosRoute />,
+                        },
+                        {
+                          path: "add",
+                          element: <SettingsMacrosAddRoute />,
+                        },
+                        {
+                          path: ":macroId/edit",
+                          element: <SettingsMacrosEditRoute />,
+                        },
+                      ],
                     },
                   ],
                 },
diff --git a/ui/src/routes/devices.$id.settings.macros.add.tsx b/ui/src/routes/devices.$id.settings.macros.add.tsx
new file mode 100644
index 0000000..1b3ce30
--- /dev/null
+++ b/ui/src/routes/devices.$id.settings.macros.add.tsx
@@ -0,0 +1,63 @@
+import { useNavigate } from "react-router-dom";
+import { useState } from "react";
+
+import { KeySequence, useMacrosStore, generateMacroId } from "@/hooks/stores";
+import { SettingsPageHeader } from "@/components/SettingsPageheader";
+import { MacroForm } from "@/components/MacroForm";
+import { DEFAULT_DELAY } from "@/constants/macros";
+import notifications from "@/notifications";
+
+export default function SettingsMacrosAddRoute() {
+  const { macros, saveMacros } = useMacrosStore();
+  const [isSaving, setIsSaving] = useState(false);
+  const navigate = useNavigate();
+
+  const normalizeSortOrders = (macros: KeySequence[]): KeySequence[] => {
+    return macros.map((macro, index) => ({
+      ...macro,
+      sortOrder: index + 1,
+    }));
+  };
+
+  const handleAddMacro = async (macro: Partial<KeySequence>) => {
+    setIsSaving(true);
+    try {
+      const newMacro: KeySequence = {
+        id: generateMacroId(),
+        name: macro.name!.trim(),
+        steps: macro.steps || [],
+        sortOrder: macros.length + 1,
+      };
+
+      await saveMacros(normalizeSortOrders([...macros, newMacro]));
+      notifications.success(`Macro "${newMacro.name}" created successfully`);
+      navigate("../");
+    } catch (error: unknown) {
+      if (error instanceof Error) {
+        notifications.error(`Failed to create macro: ${error.message}`);
+      } else {
+        notifications.error("Failed to create macro");
+      }
+    } finally {
+      setIsSaving(false);
+    }
+  };
+
+  return (
+    <div className="space-y-4">
+      <SettingsPageHeader
+        title="Add New Macro"
+        description="Create a new keyboard macro"
+      />
+      <MacroForm
+        initialData={{
+          name: "",
+          steps: [{ keys: [], modifiers: [], delay: DEFAULT_DELAY }],
+        }}
+        onSubmit={handleAddMacro}
+        onCancel={() => navigate("../")}
+        isSubmitting={isSaving}
+      />
+    </div>
+  );
+} 
\ No newline at end of file
diff --git a/ui/src/routes/devices.$id.settings.macros.edit.tsx b/ui/src/routes/devices.$id.settings.macros.edit.tsx
new file mode 100644
index 0000000..6d92420
--- /dev/null
+++ b/ui/src/routes/devices.$id.settings.macros.edit.tsx
@@ -0,0 +1,108 @@
+import { useNavigate, useParams } from "react-router-dom";
+import { useState, useEffect } from "react";
+
+import { KeySequence, useMacrosStore } from "@/hooks/stores";
+import { SettingsPageHeader } from "@/components/SettingsPageheader";
+import { MacroForm } from "@/components/MacroForm";
+import notifications from "@/notifications";
+
+const normalizeSortOrders = (macros: KeySequence[]): KeySequence[] => {
+  return macros.map((macro, index) => ({
+    ...macro,
+    sortOrder: index + 1,
+  }));
+};
+
+export default function SettingsMacrosEditRoute() {
+  const { macros, saveMacros } = useMacrosStore();
+  const [isUpdating, setIsUpdating] = useState(false);
+  const [isDeleting, setIsDeleting] = useState(false);
+  const navigate = useNavigate();
+  const { macroId } = useParams<{ macroId: string }>();
+  const [macro, setMacro] = useState<KeySequence | null>(null);
+
+  useEffect(() => {
+    const foundMacro = macros.find(m => m.id === macroId);
+    if (foundMacro) {
+      setMacro({
+        ...foundMacro,
+        steps: foundMacro.steps.map(step => ({
+          ...step,
+          keys: Array.isArray(step.keys) ? step.keys : [],
+          modifiers: Array.isArray(step.modifiers) ? step.modifiers : [],
+          delay: typeof step.delay === 'number' ? step.delay : 0
+        }))
+      });
+    } else {
+      navigate("../");
+    }
+  }, [macroId, macros, navigate]);
+
+  const handleUpdateMacro = async (updatedMacro: Partial<KeySequence>) => {
+    if (!macro) return;
+
+    setIsUpdating(true);
+    try {
+      const newMacros = macros.map(m => 
+        m.id === macro.id ? {
+          ...macro,
+          name: updatedMacro.name!.trim(),
+          steps: updatedMacro.steps || [],
+        } : m
+      );
+
+      await saveMacros(normalizeSortOrders(newMacros));
+      notifications.success(`Macro "${updatedMacro.name}" updated successfully`);
+      navigate("../");
+    } catch (error: unknown) {
+      if (error instanceof Error) {
+        notifications.error(`Failed to update macro: ${error.message}`);
+      } else {
+        notifications.error("Failed to update macro");
+      }
+    } finally {
+      setIsUpdating(false);
+    }
+  };
+
+  const handleDeleteMacro = async () => {
+    if (!macro) return;
+
+    setIsDeleting(true);
+    try {
+      const updatedMacros = normalizeSortOrders(macros.filter(m => m.id !== macro.id));
+      await saveMacros(updatedMacros);
+      notifications.success(`Macro "${macro.name}" deleted successfully`);
+      navigate("../macros");
+    } catch (error: unknown) {
+      if (error instanceof Error) {
+        notifications.error(`Failed to delete macro: ${error.message}`);
+      } else {
+        notifications.error("Failed to delete macro");
+      }
+    } finally {
+      setIsDeleting(false);
+    }
+  };
+
+  if (!macro) return null;
+
+  return (
+    <div className="space-y-4">
+      <SettingsPageHeader
+        title="Edit Macro"
+        description="Modify your keyboard macro"
+      />
+      <MacroForm
+        initialData={macro}
+        onSubmit={handleUpdateMacro}
+        onCancel={() => navigate("../")}
+        isSubmitting={isUpdating}
+        submitText="Save Changes"
+        showDelete
+        onDelete={handleDeleteMacro}
+        isDeleting={isDeleting}
+      />
+    </div>
+  );
+} 
\ No newline at end of file
diff --git a/ui/src/routes/devices.$id.settings.macros.tsx b/ui/src/routes/devices.$id.settings.macros.tsx
index 6dc4a93..c8213d1 100644
--- a/ui/src/routes/devices.$id.settings.macros.tsx
+++ b/ui/src/routes/devices.$id.settings.macros.tsx
@@ -1,90 +1,17 @@
-import { useState, useEffect, useCallback, Fragment } from "react";
-import { LuPlus, LuTrash, LuX, LuPenLine, LuLoader, LuInfo, LuCopy, LuArrowUp, LuArrowDown, LuMoveRight, LuCornerDownRight } from "react-icons/lu";
+import { useEffect, Fragment, useMemo } from "react";
+import { useNavigate } from "react-router-dom";
+import { LuPenLine, LuLoader, LuCopy, LuMoveRight, LuCornerDownRight } from "react-icons/lu";
 
-import { KeySequence, useMacrosStore } from "@/hooks/stores";
+import { KeySequence, useMacrosStore, generateMacroId } from "@/hooks/stores";
 import { SettingsPageHeader } from "@/components/SettingsPageheader";
 import { Button } from "@/components/Button";
-import Checkbox from "@/components/Checkbox";
-import { keys, modifiers, keyDisplayMap, modifierDisplayMap } from "@/keyboardMappings";
-import { useJsonRpc } from "@/hooks/useJsonRpc";
+import EmptyCard from "@/components/EmptyCard";
+import { SortableList } from "@/components/SortableList";
+import { MAX_TOTAL_MACROS, COPY_SUFFIX } from "@/constants/macros";
+import { keyDisplayMap, modifierDisplayMap } from "@/keyboardMappings";
 import notifications from "@/notifications";
 import { SettingsItem } from "@/routes/devices.$id.settings";
-import { InputFieldWithLabel, FieldError } from "@/components/InputField";
-import Fieldset from "@/components/Fieldset";
-import { SelectMenuBasic } from "@/components/SelectMenuBasic";
-import EmptyCard from "@/components/EmptyCard";
-import { Combobox } from "@/components/Combobox";
-import { CardHeader } from "@/components/CardHeader";
-import Card from "@/components/Card";
-import { SortableList } from "@/components/SortableList";
 
-const DEFAULT_DELAY = 50;
-
-interface MacroStep {
-  keys: string[];
-  modifiers: string[];
-  delay: number;
-}
-
-interface KeyOption {
-  value: string;
-  label: string;
-}
-
-interface KeyOptionData {
-  value: string | null;
-  keys?: string[];
-  label?: string;
-}
-
-const generateId = () => {
-  return `macro-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
-};
-
-// Filter out modifier keys since they're handled in the modifiers section
-const modifierKeyPrefixes = ['Alt', 'Control', 'Shift', 'Meta'];
-
-const keyOptions = Object.keys(keys)
-  .filter(key => !modifierKeyPrefixes.some(prefix => key.startsWith(prefix)))
-  .map(key => ({
-    value: key,
-    label: keyDisplayMap[key] || key,
-  }));
-
-const modifierOptions = Object.keys(modifiers).map(modifier => ({
-  value: modifier,
-  label: modifier.replace(/^(Control|Alt|Shift|Meta)(Left|Right)$/, "$1 $2"),
-}));
-
-const groupedModifiers = {
-  Control: modifierOptions.filter(mod => mod.value.startsWith('Control')),
-  Shift: modifierOptions.filter(mod => mod.value.startsWith('Shift')),
-  Alt: modifierOptions.filter(mod => mod.value.startsWith('Alt')),
-  Meta: modifierOptions.filter(mod => mod.value.startsWith('Meta')),
-};
-
-
-const PRESET_DELAYS = [
-  { value: "50", label: "50ms" },
-  { value: "100", label: "100ms" },
-  { value: "200", label: "200ms" },
-  { value: "300", label: "300ms" },
-  { value: "500", label: "500ms" },
-  { value: "750", label: "750ms" },
-  { value: "1000", label: "1000ms" },
-  { value: "1500", label: "1500ms" },
-  { value: "2000", label: "2000ms" },
-];
-
-const MAX_STEPS_PER_MACRO = 10;
-const MAX_TOTAL_MACROS = 25;
-const MAX_KEYS_PER_STEP = 10;
-
-const ensureArray = <T,>(arr: T[] | null | undefined): T[] => {
-  return Array.isArray(arr) ? arr : [];
-};
-
-// Helper function to normalize sort orders, ensuring they start at 1 and have no gaps
 const normalizeSortOrders = (macros: KeySequence[]): KeySequence[] => {
   return macros.map((macro, index) => ({
     ...macro,
@@ -92,805 +19,93 @@ const normalizeSortOrders = (macros: KeySequence[]): KeySequence[] => {
   }));
 };
 
-interface MacroStepCardProps {
-  step: MacroStep;
-  stepIndex: number;
-  onDelete?: () => void;
-  onMoveUp?: () => void;
-  onMoveDown?: () => void;
-  isDesktop: boolean;
-  onKeySelect: (option: KeyOptionData) => void;
-  onKeyQueryChange: (query: string) => void;
-  keyQuery: string;
-  getFilteredKeys: () => KeyOption[];
-  onModifierChange: (modifiers: string[]) => void;
-  onDelayChange: (delay: number) => void;
-  isLastStep: boolean;
-}
-
-function MacroStepCard({
-  step,
-  stepIndex,
-  onDelete,
-  onMoveUp,
-  onMoveDown,
-  onKeySelect,
-  onKeyQueryChange,
-  keyQuery,
-  getFilteredKeys,
-  onModifierChange,
-  onDelayChange,
-  isLastStep
-}: MacroStepCardProps) {
-  return (
-    <div className="macro-step-card rounded-md border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 p-4 shadow-sm">
-      <div className="mb-2 flex items-center justify-between">
-        <div className="flex items-center gap-1.5">
-          <div className="flex items-center gap-1">
-            <Button
-              size="XS"
-              theme="light"
-              onClick={onMoveUp}
-              disabled={stepIndex === 0}
-              LeadingIcon={LuArrowUp}
-            />
-            <Button
-              size="XS"
-              theme="light"
-              onClick={onMoveDown}
-              disabled={isLastStep}
-              LeadingIcon={LuArrowDown}
-            />
-          </div>
-          <span className="macro-step-number flex h-5 w-5 items-center justify-center rounded-full bg-blue-100 text-xs font-semibold text-blue-700 dark:bg-blue-900/40 dark:text-blue-200">
-            {stepIndex + 1}
-          </span>
-        </div>
-        
-        <div className="flex items-center space-x-2">
-          {onDelete && (
-            <Button
-              size="XS"
-              theme="danger"
-              text="Delete"
-              onClick={onDelete}
-              LeadingIcon={LuTrash}
-              />
-          )}
-        </div>
-      </div>
-      
-      <div className="space-y-4 mt-2">
-        <div className="w-full flex flex-col gap-2">
-          <label className="text-sm font-medium text-slate-700 dark:text-slate-300">
-            Modifiers:
-          </label>
-          <div className="macro-modifiers-container inline-flex flex-wrap gap-3">
-            {Object.entries(groupedModifiers).map(([group, mods]) => (
-              <div key={group} className="relative min-w-[120px] rounded-md border border-slate-200 dark:border-slate-700 p-2">
-                <span className="absolute -top-2.5 left-2 px-1 text-xs font-medium bg-white dark:bg-slate-800 text-slate-500 dark:text-slate-400">
-                  {group}
-                </span>
-                <div className="flex flex-wrap gap-1">
-                  {mods.map(option => (
-                    <label 
-                      key={option.value} 
-                      className={`flex items-center px-2 py-1 rounded border cursor-pointer text-xs font-medium transition-colors ${
-                        ensureArray(step.modifiers).includes(option.value) 
-                          ? 'bg-blue-100 border-blue-300 text-blue-700 dark:bg-blue-900/40 dark:border-blue-600 dark:text-blue-200' 
-                          : 'bg-slate-100 border-slate-200 text-slate-600 hover:bg-slate-200 dark:bg-slate-800 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-700'
-                      }`}
-                    >
-                      <Checkbox
-                        className="sr-only"
-                        size="SM"
-                        checked={ensureArray(step.modifiers).includes(option.value)}
-                        onChange={e => {
-                          const modifiersArray = ensureArray(step.modifiers);
-                          const newModifiers = e.target.checked
-                            ? [...modifiersArray, option.value]
-                            : modifiersArray.filter(m => m !== option.value);
-                          onModifierChange(newModifiers);
-                        }}
-                      />
-                      {option.label.split(' ')[1] || option.label}
-                    </label>
-                  ))}
-                </div>
-              </div>
-            ))}
-          </div>
-        </div>
-        
-        <div className="w-full flex flex-col gap-1">
-          <label className="text-sm font-medium text-slate-700 dark:text-slate-300">
-            Keys:
-          </label>
-          
-          <div className="macro-key-group flex flex-wrap gap-1 pb-2">
-            {ensureArray(step.keys).map((key, keyIndex) => (
-              <span
-                key={keyIndex}
-                className="inline-flex items-center rounded-md bg-blue-100 px-1 text-xs font-medium text-blue-800 dark:bg-blue-900/40 dark:text-blue-200"
-              >
-                <span className="px-1">
-                  {keyDisplayMap[key] || key}
-                </span>
-                <Button
-                  size="XS"
-                  className=""
-                  theme="blank"
-                  onClick={() => {
-                    const newKeys = ensureArray(step.keys).filter((_, i) => i !== keyIndex);
-                    onKeySelect({ value: null, keys: newKeys });
-                  }}
-                  LeadingIcon={LuX}
-                />
-              </span>
-            ))}
-          </div>
-          <div className="relative w-full">
-            <Combobox<KeyOption>
-              onChange={(value: KeyOption) => onKeySelect(value)}
-              displayValue={() => keyQuery}
-              onInputChange={onKeyQueryChange}
-              options={getFilteredKeys}
-              disabledMessage="Max keys reached"
-              size="SM"
-              disabled={ensureArray(step.keys).length >= MAX_KEYS_PER_STEP}
-              placeholder={ensureArray(step.keys).length >= MAX_KEYS_PER_STEP ? "Max keys reached" : "Search for key..."}
-              emptyMessage="No matching keys found"
-            />
-          </div>
-        </div>
-        
-        <div className="w-full flex flex-col gap-1">
-          <div className="flex items-center gap-1">
-            <label className="text-sm font-medium text-slate-700 dark:text-slate-300">
-              Step Duration:
-            </label>
-            <div className="group relative">
-              <LuInfo className="h-4 w-4 text-slate-400 hover:text-slate-600 dark:text-slate-500 dark:hover:text-slate-400" />
-              <div className="absolute left-1/2 top-full z-10 mt-1 hidden w-64 -translate-x-1/2 rounded-md bg-slate-800 px-3 py-2 text-xs text-white shadow-lg group-hover:block dark:bg-slate-700">
-                <p>The time to wait after pressing the keys in this step before moving to the next step. This helps ensure reliable key presses when automating keyboard input.</p>
-              </div>
-            </div>
-          </div>
-          <div className="flex items-center gap-3">
-            <SelectMenuBasic
-              size="SM"
-              fullWidth
-              value={step.delay.toString()}
-              onChange={(e) => onDelayChange(parseInt(e.target.value, 10))}
-              options={PRESET_DELAYS}
-            />
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-}
-
-// Helper to update step keys used by both new and edit flows
-const updateStepKeys = (
-  steps: MacroStep[],
-  stepIndex: number,
-  keyOption: { value: string | null; keys?: string[] },
-  showTemporaryError: (msg: string) => void
-) => {
-  const newSteps = [...steps];
-  
-  // Check if the step at stepIndex exists
-  if (!newSteps[stepIndex]) {
-    console.error(`Step at index ${stepIndex} does not exist`);
-    return steps; // Return original steps to avoid mutation
-  }
-  
-  if (keyOption.keys) {
-    newSteps[stepIndex].keys = keyOption.keys;
-  } else if (keyOption.value) {
-    // Initialize keys array if it doesn't exist
-    if (!newSteps[stepIndex].keys) {
-      newSteps[stepIndex].keys = [];
-    }
-    const keysArray = ensureArray(newSteps[stepIndex].keys);
-    if (keysArray.length >= MAX_KEYS_PER_STEP) {
-      showTemporaryError(`Maximum of ${MAX_KEYS_PER_STEP} keys per step allowed`);
-      return newSteps;
-    }
-    newSteps[stepIndex].keys = [...keysArray, keyOption.value];
-  }
-  return newSteps;
-};
-
-interface StepError {
-  keys?: string;
-  modifiers?: string;
-  delay?: string;
-}
-
-interface ValidationErrors {
-  name?: string;
-  description?: string;
-  steps?: Record<number, StepError>;
-}
-
 export default function SettingsMacrosRoute() {
-  const { macros, loading, initialized, loadMacros, saveMacros, setSendFn } = useMacrosStore();
-  const [editingMacro, setEditingMacro] = useState<KeySequence | null>(null);
-  const [newMacro, setNewMacro] = useState<Partial<KeySequence>>({
-    name: "",
-    description: "",
-    steps: [{ keys: [], modifiers: [], delay: DEFAULT_DELAY }],
-  });
-  const [macroToDelete, setMacroToDelete] = useState<string | null>(null);
+  const { macros, loading, initialized, loadMacros, saveMacros } = useMacrosStore();
+  const navigate = useNavigate();
   
-  const [keyQueries, setKeyQueries] = useState<Record<number, string>>({});
-  const [editKeyQueries, setEditKeyQueries] = useState<Record<number, string>>({});
-  
-  const [errorMessage, setErrorMessage] = useState<string | null>(null);
-  const [isDesktop, setIsDesktop] = useState(window.innerWidth >= 768);
-  
-  const [send] = useJsonRpc();
+  const isMaxMacrosReached = useMemo(() => 
+    macros.length >= MAX_TOTAL_MACROS, 
+    [macros.length]
+  );
 
-  const isMaxMacrosReached = macros.length >= MAX_TOTAL_MACROS;
-  const isMaxStepsReachedForNewMacro = (newMacro.steps?.length || 0) >= MAX_STEPS_PER_MACRO;
-  
-  const showTemporaryError = useCallback((message: string) => {
-    setErrorMessage(message);
-    setTimeout(() => setErrorMessage(null), 3000);
-  }, []);
-  
-  // Helper for both new and edit key select
-  const handleKeySelectUpdate = (stepIndex: number, option: KeyOptionData, isEditing = false) => {
-    if (isEditing && editingMacro) {
-      const updatedSteps = updateStepKeys(editingMacro.steps, stepIndex, option, showTemporaryError);
-      setEditingMacro({ ...editingMacro, steps: updatedSteps });
-    } else {
-      const updatedSteps = updateStepKeys(newMacro.steps || [], stepIndex, option, showTemporaryError);
-      setNewMacro({ ...newMacro, steps: updatedSteps });
-    }
-  };
-  
-  const handleKeySelect = (stepIndex: number, option: KeyOptionData) => {
-    handleKeySelectUpdate(stepIndex, option, false);
-  };
-  
-  const handleEditKeySelect = (stepIndex: number, option: KeyOptionData) => {
-    handleKeySelectUpdate(stepIndex, option, true);
-  };
-  
-  const handleKeyQueryChange = (stepIndex: number, query: string) => {
-    setKeyQueries(prev => ({ ...prev, [stepIndex]: query }));
-  };
-  
-  const handleEditKeyQueryChange = (stepIndex: number, query: string) => {
-    setEditKeyQueries(prev => ({ ...prev, [stepIndex]: query }));
-  };
-  
-  const getFilteredKeys = (stepIndex: number, isEditing = false) => {
-    const query = isEditing 
-      ? (editKeyQueries[stepIndex] || '')
-      : (keyQueries[stepIndex] || '');
-    
-    const currentStep = isEditing 
-      ? editingMacro?.steps[stepIndex] 
-      : newMacro.steps?.[stepIndex];
-    
-    const selectedKeys = ensureArray(currentStep?.keys);
-    
-    const availableKeys = keyOptions.filter(option => !selectedKeys.includes(option.value));
-    
-    if (query === '') {
-      return availableKeys;
-    } else {
-      return availableKeys.filter(option => option.label.toLowerCase().includes(query.toLowerCase()));
-    }
-  };
-  
   useEffect(() => {
-    setSendFn(send);
     if (!initialized) {
       loadMacros();
     }
-  }, [initialized, loadMacros, setSendFn, send]);
-  
-  const [errors, setErrors] = useState<ValidationErrors>({});
-  
-  const clearErrors = useCallback(() => {
-    setErrors({});
-  }, []);
-  
-  const validateMacro = (macro: Partial<KeySequence>): ValidationErrors => {
-    const errors: ValidationErrors = {};
-
-    // Name validation
-    if (!macro.name?.trim()) {
-      errors.name = "Name is required";
-    } else if (macro.name.trim().length > 50) {
-      errors.name = "Name must be less than 50 characters";
-    }
-
-    // Description validation (optional)
-    if (macro.description && macro.description.trim().length > 200) {
-      errors.description = "Description must be less than 200 characters";
-    }
-
-    // Steps validation
-    if (!macro.steps?.length) {
-      errors.steps = { 0: { keys: "At least one step is required" } };
-      return errors;
-    }
-
-    // Check if at least one step has keys or modifiers
-    const hasKeyOrModifier = macro.steps.some(step => 
-      (step.keys?.length || 0) > 0 || (step.modifiers?.length || 0) > 0
-    );
-
-    if (!hasKeyOrModifier) {
-      errors.steps = { 0: { keys: "At least one step must have keys or modifiers" } };
-      return errors;
-    }
-
-    const stepErrors: Record<number, StepError> = {};
-    
-    macro.steps.forEach((step, index) => {
-      const stepError: StepError = {};
-
-      // Keys validation (only if keys are present)
-      if (step.keys?.length && step.keys.length > MAX_KEYS_PER_STEP) {
-        stepError.keys = `Maximum ${MAX_KEYS_PER_STEP} keys allowed`;
-      }
-
-      // Delay validation
-      if (typeof step.delay !== 'number' || step.delay < 0) {
-        stepError.delay = "Invalid delay value";
-      }
-
-      if (Object.keys(stepError).length > 0) {
-        stepErrors[index] = stepError;
-      }
-    });
-
-    if (Object.keys(stepErrors).length > 0) {
-      errors.steps = stepErrors;
-    }
-
-    return errors;
-  };
-
-  const resetNewMacro = () => {
-    setNewMacro({
-      name: "",
-      description: "",
-      steps: [{ keys: [], modifiers: [], delay: DEFAULT_DELAY }],
-    });
-    setKeyQueries({});
-    setErrors({});
-  };
-
-  const [isSaving, setIsSaving] = useState(false);
-  const [isUpdating, setIsUpdating] = useState(false);
-  const [isDeleting, setIsDeleting] = useState(false);
-
-  const handleAddMacro = useCallback(async () => {
-    if (isMaxMacrosReached) {
-      showTemporaryError(`Maximum of ${MAX_TOTAL_MACROS} macros allowed`);
-      return;
-    }
-
-    const validationErrors = validateMacro(newMacro);
-    if (Object.keys(validationErrors).length > 0) {
-      setErrors(validationErrors);
-      return;
-    }
-
-    setIsSaving(true);
-    try {
-      const macro: KeySequence = {
-        id: generateId(),
-        name: newMacro.name!.trim(),
-        description: newMacro.description?.trim() || "",
-        steps: newMacro.steps || [],
-        sortOrder: macros.length + 1,
-      };
-
-      await saveMacros(normalizeSortOrders([...macros, macro]));
-      resetNewMacro();
-      setShowAddMacro(false);
-      notifications.success(`Macro "${macro.name}" created successfully`);
-    } catch (error) {
-      if (error instanceof Error) {
-        notifications.error(`Failed to create macro: ${error.message}`);
-        showTemporaryError(error.message);
-      } else {
-        notifications.error("Failed to create macro");
-        showTemporaryError("Failed to save macro");
-      }
-    } finally {
-      setIsSaving(false);
-    }
-  }, [isMaxMacrosReached, newMacro, macros, saveMacros, showTemporaryError]);
-
-  const handleEditMacro = (macro: KeySequence) => {
-    setEditingMacro({
-      ...macro,
-      description: macro.description || "",
-      steps: macro.steps.map(step => ({
-        ...step,
-        keys: ensureArray(step.keys),
-        modifiers: ensureArray(step.modifiers),
-        delay: typeof step.delay === 'number' ? step.delay : DEFAULT_DELAY
-      }))
-    });
-    clearErrors();
-    setEditKeyQueries({});
-  };
-
-  const handleDeleteMacro = async (id: string) => {
-    const macroToBeDeleted = macros.find(m => m.id === id);
-    if (!macroToBeDeleted) return;
-
-    setIsDeleting(true);
-    try {
-      const updatedMacros = normalizeSortOrders(macros.filter(macro => macro.id !== id));
-      await saveMacros(updatedMacros);
-      if (editingMacro?.id === id) {
-        setEditingMacro(null);
-      }
-      setMacroToDelete(null);
-      notifications.success(`Macro "${macroToBeDeleted.name}" deleted successfully`);
-    } catch (error) {
-      if (error instanceof Error) {
-        notifications.error(`Failed to delete macro: ${error.message}`);
-        showTemporaryError(error.message);
-      } else {
-        notifications.error("Failed to delete macro");
-        showTemporaryError("Failed to delete macro");
-      }
-    } finally {
-      setIsDeleting(false);
-    }
-  };
+  }, [initialized, loadMacros]);
 
   const handleDuplicateMacro = async (macro: KeySequence) => {
     if (isMaxMacrosReached) {
-      showTemporaryError(`Maximum of ${MAX_TOTAL_MACROS} macros allowed`);
+      notifications.error(`Maximum of ${MAX_TOTAL_MACROS} macros allowed`);
       return;
     }
 
     const newMacroCopy: KeySequence = {
       ...JSON.parse(JSON.stringify(macro)),
-      id: generateId(),
-      name: `${macro.name} (copy)`,
+      id: generateMacroId(),
+      name: `${macro.name} ${COPY_SUFFIX}`,
       sortOrder: macros.length + 1,
     };
 
-    newMacroCopy.steps = newMacroCopy.steps.map(step => ({
-      ...step,
-      keys: ensureArray(step.keys),
-      modifiers: ensureArray(step.modifiers),
-      delay: step.delay || DEFAULT_DELAY
-    }));
-
     try {
       await saveMacros(normalizeSortOrders([...macros, newMacroCopy]));
       notifications.success(`Macro "${newMacroCopy.name}" duplicated successfully`);
-    } catch (error) {
+    } catch (error: unknown) {
       if (error instanceof Error) {
         notifications.error(`Failed to duplicate macro: ${error.message}`);
-        showTemporaryError(error.message);
       } else {
         notifications.error("Failed to duplicate macro");
-        showTemporaryError("Failed to duplicate macro");
       }
     }
   };
 
-  const handleStepMove = (stepIndex: number, direction: 'up' | 'down', steps: MacroStep[]) => {
-    const newSteps = [...steps];
-    const newIndex = direction === 'up' ? stepIndex - 1 : stepIndex + 1;
-    [newSteps[stepIndex], newSteps[newIndex]] = [newSteps[newIndex], newSteps[stepIndex]];
-    return newSteps;
-  };
-
-  useEffect(() => {
-    const handleResize = () => {
-      setIsDesktop(window.innerWidth >= 768);
-    };
-    
-    window.addEventListener('resize', handleResize);
-    return () => window.removeEventListener('resize', handleResize);
-  }, []);
-
-  const [showClearConfirm, setShowClearConfirm] = useState(false);
-  const [showAddMacro, setShowAddMacro] = useState(false);
-
-  const handleUpdateMacro = useCallback(async () => {
-    if (!editingMacro) return;
-
-    const validationErrors = validateMacro(editingMacro);
-    if (Object.keys(validationErrors).length > 0) {
-      setErrors(validationErrors);
-      return;
-    }
-
-    setIsUpdating(true);
-    try {
-      const newMacros = macros.map(m => 
-        m.id === editingMacro.id ? {
-          ...editingMacro,
-          name: editingMacro.name.trim(),
-          description: editingMacro.description?.trim() || "",
-        } : m
-      );
-
-      await saveMacros(normalizeSortOrders(newMacros));
-      setEditingMacro(null);
-      clearErrors();
-      notifications.success(`Macro "${editingMacro.name}" updated successfully`);
-    } catch (error) {
-      if (error instanceof Error) {
-        notifications.error(`Failed to update macro: ${error.message}`);
-        showTemporaryError(error.message);
-      } else {
-        notifications.error("Failed to update macro");
-        showTemporaryError("Failed to update macro");
-      }
-    } finally {
-      setIsUpdating(false);
-    }
-  }, [editingMacro, macros, saveMacros, showTemporaryError, clearErrors]);
-
-  useEffect(() => {
-    const handleKeyDown = (e: KeyboardEvent) => {
-      if (e.key === 'Escape' && editingMacro) {
-        setEditingMacro(null);
-        setErrors({});
-      }
-      if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
-        if (editingMacro) {
-          handleUpdateMacro();
-        } else if (!isMaxMacrosReached) {
-          handleAddMacro();
-        }
-      }
-    };
-
-    window.addEventListener('keydown', handleKeyDown);
-    return () => window.removeEventListener('keydown', handleKeyDown);
-  }, [editingMacro, isMaxMacrosReached, handleAddMacro, handleUpdateMacro]);
-
-  const handleModifierChange = (stepIndex: number, modifiers: string[]) => {
-    if (editingMacro) {
-      const newSteps = [...editingMacro.steps];
-      newSteps[stepIndex].modifiers = modifiers;
-      setEditingMacro({ ...editingMacro, steps: newSteps });
-    } else {
-      const newSteps = [...(newMacro.steps || [])];
-      newSteps[stepIndex].modifiers = modifiers;
-      setNewMacro({ ...newMacro, steps: newSteps });
-    }
-  };
-
-  const handleDelayChange = (stepIndex: number, delay: number) => {
-    if (editingMacro) {
-      const newSteps = [...editingMacro.steps];
-      newSteps[stepIndex].delay = delay;
-      setEditingMacro({ ...editingMacro, steps: newSteps });
-    } else {
-      const newSteps = [...(newMacro.steps || [])];
-      newSteps[stepIndex].delay = delay;
-      setNewMacro({ ...newMacro, steps: newSteps });
-    }
-  };
-
-  const ErrorMessage = ({ error }: { error?: string }) => {
-    if (!error) return null;
-    return (
-      <FieldError error={error} />
-    );
-  };
   return (
     <div className="space-y-4">
-          <SettingsPageHeader
-            title="Keyboard Macros"
-            description="Create and manage keyboard macros for quick actions"
-        />
-        {macros.length > 0 && (
-          <div className="flex items-center justify-between mb-4">
-            <SettingsItem 
-              title="Macros"
-              description={`${macros.length}/${MAX_TOTAL_MACROS}`}
-            >
-              <div className="flex items-center gap-2">
-                {!showAddMacro && (
-                  <Button
-                    size="SM"
-                    theme="primary"
-                    text={isMaxMacrosReached ? `Max Reached` : "Add New Macro"}
-                    onClick={() => setShowAddMacro(true)}
-                    disabled={isMaxMacrosReached}
-                  />
-                )}
-              </div>
-            </SettingsItem>
-          </div>
-        )}
-
-      {errorMessage && (<FieldError error={errorMessage} />)}
-
-      {loading && (
-        <div className="flex items-center justify-center p-8">
-          <LuLoader className="h-6 w-6 animate-spin text-blue-500" />
-        </div>
-      )}
-      <div className={`space-y-4 ${loading ? 'hidden' : ''}`}>
-        {showAddMacro && (
-          <Card className="p-3">
-            <CardHeader
-              headline="Add New Macro"
+      <SettingsPageHeader
+        title="Keyboard Macros"
+        description="Create and manage keyboard macros for quick actions"
+      />
+      <div className="flex items-center justify-between mb-4">
+        <SettingsItem 
+          title="Macros"
+          description={`${loading ? '?' : macros.length}/${MAX_TOTAL_MACROS}`}
+        >
+          <div className="flex items-center gap-2">
+            <Button
+              size="SM"
+              theme="primary"
+              text={isMaxMacrosReached ? `Max Reached` : "Add New Macro"}
+              onClick={() => navigate("add")}
+              disabled={isMaxMacrosReached}
             />
-            <Fieldset className="mt-4">
-              <InputFieldWithLabel
-                type="text"
-                label="Macro Name"
-                placeholder="Macro Name"
-                value={newMacro.name}
-                error={errors.name}
-                onChange={e => {
-                  setNewMacro(prev => ({ ...prev, name: e.target.value }));
-                  if (errors.name) {
-                    const newErrors = { ...errors };
-                    delete newErrors.name;
-                    setErrors(newErrors);
-                  }
-                }}
-              />
-            </Fieldset>
-            
-            <div className="mt-4">
-              <div className="macro-section-header">
-                <label className="macro-section-title">
-                  Steps:
-                </label>
-                <span className="macro-section-subtitle">
-                  {newMacro.steps?.length || 0}/{MAX_STEPS_PER_MACRO} steps
-                </span>
-              </div>
-              {errors.steps && errors.steps[0]?.keys && (
-                <div className="mt-2">
-                  <ErrorMessage error={errors.steps[0].keys} />
-                </div>
-              )}
-              <div className="mt-2 text-xs text-slate-500 dark:text-slate-400">
-                You can add up to {MAX_STEPS_PER_MACRO} steps per macro
-              </div>
-              <Fieldset>
-                <div className="mt-2 space-y-4">
-                  {(newMacro.steps || []).map((step, stepIndex) => (
-                    <MacroStepCard
-                      key={stepIndex}
-                      step={step}
-                      stepIndex={stepIndex}
-                      onDelete={newMacro.steps && newMacro.steps.length > 1 ? () => {
-                        const newSteps = [...(newMacro.steps || [])];
-                        newSteps.splice(stepIndex, 1);
-                        setNewMacro(prev => ({ ...prev, steps: newSteps }));
-                      } : undefined}
-                      onMoveUp={() => {
-                        const newSteps = handleStepMove(stepIndex, 'up', newMacro.steps || []);
-                        setNewMacro(prev => ({ ...prev, steps: newSteps }));
-                      }}
-                      onMoveDown={() => {
-                        const newSteps = handleStepMove(stepIndex, 'down', newMacro.steps || []);
-                        setNewMacro(prev => ({ ...prev, steps: newSteps }));
-                      }}
-                      isDesktop={isDesktop}
-                      onKeySelect={(option) => handleKeySelect(stepIndex, option)}
-                      onKeyQueryChange={(query) => handleKeyQueryChange(stepIndex, query)}
-                      keyQuery={keyQueries[stepIndex] || ''}
-                      getFilteredKeys={() => getFilteredKeys(stepIndex)}
-                      onModifierChange={(modifiers) => handleModifierChange(stepIndex, modifiers)}
-                      onDelayChange={(delay) => handleDelayChange(stepIndex, delay)}
-                      isLastStep={stepIndex === (newMacro.steps?.length || 0) - 1}
-                    />
-                  ))}
-                </div>
-              </Fieldset>
+          </div>
+        </SettingsItem>
+      </div>
 
-              <div className="mt-4 border-t border-slate-200 pt-4 dark:border-slate-700">
-                <Button
-                  size="MD"
-                  theme="light"
-                  fullWidth
-                  LeadingIcon={LuPlus}
-                  text={`Add Step ${isMaxStepsReachedForNewMacro ? `(${MAX_STEPS_PER_MACRO} max)` : ''}`}
-                  onClick={() => {
-                    if (isMaxStepsReachedForNewMacro) {
-                      showTemporaryError(`You can only add a maximum of ${MAX_STEPS_PER_MACRO} steps per macro.`);
-                      return;
-                    }
-                    
-                    setNewMacro(prev => ({
-                      ...prev,
-                      steps: [
-                        ...(prev.steps || []), 
-                        { keys: [], modifiers: [], delay: DEFAULT_DELAY }
-                      ],
-                    }));
-                    clearErrors();
-                  }}
-                  disabled={isMaxStepsReachedForNewMacro}
-                />
-              </div>
-
-              <div className="mt-6 flex items-center justify-between border-t border-slate-200 pt-4 dark:border-slate-700">
-                {showClearConfirm ? (
-                  <div className="flex items-center gap-2">
-                    <span className="text-sm text-slate-600 dark:text-slate-400">
-                      Cancel changes?
-                    </span>
-                    <Button
-                      size="SM"
-                      theme="danger"
-                      text="Yes"
-                      onClick={() => {
-                        resetNewMacro();
-                        setShowAddMacro(false);
-                        setShowClearConfirm(false);
-                      }}
-                    />
-                    <Button
-                      size="SM"
-                      theme="light"
-                      text="No"
-                      onClick={() => setShowClearConfirm(false)}
-                    />
-                  </div>
-                ) : (
-                  <div className="flex gap-x-2">
-                    <Button
-                      size="SM"
-                      theme="primary"
-                      text={isSaving ? "Saving..." : "Save Macro"}
-                      onClick={handleAddMacro}
-                      disabled={isSaving}
-                    />
-                    <Button
-                      size="SM"
-                      theme="light"
-                      text="Cancel"
-                      onClick={() => {
-                        if (newMacro.name || newMacro.description || newMacro.steps?.some(s => s.keys?.length || s.modifiers?.length)) {
-                          setShowClearConfirm(true);
-                        } else {
-                          resetNewMacro();
-                          setShowAddMacro(false);
-                        }
-                      }}
-                    />
-                  </div>
-                )}
-              </div>
-            </div>
-          </Card>
-        )}
-        {macros.length === 0 && !showAddMacro && (
+      <div className="space-y-4">
+        {loading ? (
+          <EmptyCard
+            headline="Loading macros..."
+            description="Please wait while we fetch your macros"
+            BtnElm={
+              <LuLoader className="h-6 w-6 animate-spin text-blue-500" />
+            }
+          />
+        ) : macros.length === 0 ? (
           <EmptyCard
             headline="No macros created yet"
+            description="Create keyboard macros to automate repetitive tasks"
             BtnElm={
               <Button
                 size="SM"
                 theme="primary"
                 text="Add New Macro"
-                onClick={() => setShowAddMacro(true)}
+                onClick={() => navigate("add")}
                 disabled={isMaxMacrosReached}
               />
             }
           />
-        )}
-        {macros.length > 0 && (
+        ) : (
           <SortableList<KeySequence>
             keyFn={(macro) => macro.id}
             items={macros}
@@ -900,248 +115,88 @@ export default function SettingsMacrosRoute() {
               try {
                 await saveMacros(updatedMacros);
                 notifications.success("Macro order updated successfully");
-              } catch (error) {
+              } catch (error: unknown) {
                 if (error instanceof Error) {
                   notifications.error(`Failed to reorder macros: ${error.message}`);
-                  showTemporaryError(error.message);
                 } else {
                   notifications.error("Failed to reorder macros");
-                  showTemporaryError("Failed to save reordered macros");
                 }
               }
             }}
-            disabled={!!editingMacro}
             variant="list"
             size="XS"
             handlePosition="left"
           >
             {(macro) => (
-              editingMacro && editingMacro.id === macro.id ? (
-                <Card className="border-blue-300 bg-blue-50 p-3 dark:border-blue-700 dark:bg-blue-900/20">
-                  <CardHeader
-                    headline="Edit Macro"
-                  />
-                  <Fieldset className="mt-4">
-                    <InputFieldWithLabel
-                      type="text"
-                      label="Macro Name"
-                      placeholder="Macro Name"
-                      value={editingMacro.name}
-                      error={errors.name}
-                      onChange={e => {
-                        setEditingMacro({ ...editingMacro, name: e.target.value });
-                        if (errors.name) {
-                          const newErrors = { ...errors };
-                          delete newErrors.name;
-                          setErrors(newErrors);
-                        }
-                      }}
-                    />
-                  </Fieldset>
-                  
-                  <div className="mt-4">
-                    <div className="flex items-center justify-between">
-                      <label className="text-sm font-medium text-slate-700 dark:text-slate-300">
-                        Steps:
-                      </label>
-                      <span className="text-sm text-slate-500 dark:text-slate-400">
-                        {editingMacro.steps.length}/{MAX_STEPS_PER_MACRO} steps
-                      </span>
-                    </div>
-                    {errors.steps && errors.steps[0]?.keys && (
-                      <div className="mt-2">
-                        <ErrorMessage error={errors.steps[0].keys} />
-                      </div>
-                    )}
-                    <div className="mt-2 text-xs text-slate-500 dark:text-slate-400">
-                      You can add up to {MAX_STEPS_PER_MACRO} steps per macro
-                    </div>
-                    <Fieldset>
-                      <div className="mt-2 space-y-4">
-                        {editingMacro.steps.map((step, stepIndex) => (
-                          <MacroStepCard
-                            key={stepIndex}
-                            step={step}
-                            stepIndex={stepIndex}
-                            onDelete={editingMacro.steps.length > 1 ? () => {
-                              const newSteps = [...editingMacro.steps];
-                              newSteps.splice(stepIndex, 1);
-                              setEditingMacro({ ...editingMacro, steps: newSteps });
-                            } : undefined}
-                            onMoveUp={() => {
-                              const newSteps = handleStepMove(stepIndex, 'up', editingMacro.steps);
-                              setEditingMacro({ ...editingMacro, steps: newSteps });
-                            }}
-                            onMoveDown={() => {
-                              const newSteps = handleStepMove(stepIndex, 'down', editingMacro.steps);
-                              setEditingMacro({ ...editingMacro, steps: newSteps });
-                            }}
-                            isDesktop={isDesktop}
-                            onKeySelect={(option) => handleEditKeySelect(stepIndex, option)}
-                            onKeyQueryChange={(query) => handleEditKeyQueryChange(stepIndex, query)}
-                            keyQuery={editKeyQueries[stepIndex] || ''}
-                            getFilteredKeys={() => getFilteredKeys(stepIndex, true)}
-                            onModifierChange={(modifiers) => handleModifierChange(stepIndex, modifiers)}
-                            onDelayChange={(delay) => handleDelayChange(stepIndex, delay)}
-                            isLastStep={stepIndex === editingMacro.steps.length - 1}
-                          />
-                        ))}
-                      </div>
-                    </Fieldset>
-                    
-                    <div className="mt-4 border-t border-slate-200 pt-4 dark:border-slate-700">
-                      <Button
-                        size="MD"
-                        theme="light"
-                        fullWidth
-                        LeadingIcon={LuPlus}
-                        text={`Add Step ${editingMacro.steps.length >= MAX_STEPS_PER_MACRO ? `(${MAX_STEPS_PER_MACRO} max)` : ''}`}
-                        onClick={() => {
-                          if (editingMacro.steps.length >= MAX_STEPS_PER_MACRO) {
-                            showTemporaryError(`You can only add a maximum of ${MAX_STEPS_PER_MACRO} steps per macro.`);
-                            return;
-                          }
-                          
-                          setEditingMacro({
-                            ...editingMacro,
-                            steps: [
-                              ...editingMacro.steps, 
-                              { keys: [], modifiers: [], delay: DEFAULT_DELAY }
-                            ],
-                          });
-                          clearErrors();
-                        }}
-                        disabled={editingMacro.steps.length >= MAX_STEPS_PER_MACRO}
-                      />
-                    </div>
+              <div className="flex items-center justify-between">
+                <div className="flex-1 min-w-0 flex flex-col justify-center">
+                  <h3 className="truncate text-sm font-semibold text-black dark:text-white">
+                    {macro.name}
+                  </h3>
+                  <p className="mt-1 ml-2 text-xs text-slate-500 dark:text-slate-400 overflow-hidden">
+                    <span className="flex flex-col items-start gap-1">
+                      {macro.steps.map((step, stepIndex) => {
+                        const StepIcon = stepIndex === 0 ? LuMoveRight : LuCornerDownRight;
 
-                    <div className="mt-4 flex items-center justify-between border-t border-slate-200 pt-4 dark:border-slate-700">
-                      <div className="flex gap-x-2">
-                        <Button
-                          size="SM"
-                          theme="primary"
-                          text={isUpdating ? "Saving..." : "Save Changes"}
-                          onClick={handleUpdateMacro}
-                          disabled={isUpdating}
-                        />
-                        <Button
-                          size="SM"
-                          theme="light"
-                          text="Cancel"
-                          onClick={() => {
-                            setEditingMacro(null);
-                            setErrors({});
-                          }}
-                        />
-                      </div>
-                    </div>
-                  </div>
-                </Card>
-              ) : (
-                <div className="flex items-center justify-between">
-                  <div className="flex-1 min-w-0 flex flex-col justify-center">
-                    <h3 className="truncate text-sm font-semibold text-black dark:text-white">
-                      {macro.name}
-                    </h3>
-                    <p className="mt-1 ml-2 text-xs text-slate-500 dark:text-slate-400 overflow-hidden">
-                      <span className="flex flex-col items-start gap-1">
-                        {macro.steps.map((step, stepIndex) => {
-                          const StepIcon = stepIndex === 0 ? LuMoveRight : LuCornerDownRight;
-
-                          return (
-                            <span key={stepIndex} className="inline-flex items-center">
-                              <StepIcon className="mr-1 text-slate-400 dark:text-slate-500 h-3 w-3 flex-shrink-0" />
-                              <span className="bg-slate-50 dark:bg-slate-800 px-2 py-0.5 rounded-md border border-slate-200/50 dark:border-slate-700/50">
-                                {(ensureArray(step.modifiers).length > 0 || ensureArray(step.keys).length > 0) ? (
-                                  <>
-                                    {ensureArray(step.modifiers).map((modifier, idx) => (
-                                      <Fragment key={`mod-${idx}`}>
-                                        <span className="font-medium text-slate-600 dark:text-slate-200">
-                                          {modifierDisplayMap[modifier] || modifier}
-                                        </span>
-                                        {idx < ensureArray(step.modifiers).length - 1 && (
-                                          <span className="text-slate-400 dark:text-slate-600"> + </span>
-                                        )}
-                                      </Fragment>
-                                    ))}
-                                    
-                                    {ensureArray(step.modifiers).length > 0 && ensureArray(step.keys).length > 0 && (
-                                      <span className="text-slate-400 dark:text-slate-600"> + </span>
-                                    )}
-                                    
-                                    {ensureArray(step.keys).map((key, idx) => (
-                                      <Fragment key={`key-${idx}`}>
-                                        <span className="font-medium text-blue-600 dark:text-blue-200">
-                                          {keyDisplayMap[key] || key}
-                                        </span>
-                                        {idx < ensureArray(step.keys).length - 1 && (
-                                          <span className="text-slate-400 dark:text-slate-600"> + </span>
-                                        )}
-                                      </Fragment>
-                                    ))}
-                                  </>
-                                ) : (
-                                  <span className="font-medium text-slate-500 dark:text-slate-400">Delay only</span>
-                                )}
-                                <span className="ml-1 text-slate-400 dark:text-slate-500">({step.delay}ms)</span>
-                              </span>
+                        return (
+                          <span key={stepIndex} className="inline-flex items-center">
+                            <StepIcon className="mr-1 text-slate-400 dark:text-slate-500 h-3 w-3 flex-shrink-0" />
+                            <span className="bg-slate-50 dark:bg-slate-800 px-2 py-0.5 rounded-md border border-slate-200/50 dark:border-slate-700/50">
+                              {(Array.isArray(step.modifiers) && step.modifiers.length > 0) || (Array.isArray(step.keys) && step.keys.length > 0) ? (
+                                <>
+                                  {Array.isArray(step.modifiers) && step.modifiers.map((modifier, idx) => (
+                                    <Fragment key={`mod-${idx}`}>
+                                      <span className="font-medium text-slate-600 dark:text-slate-200">
+                                        {modifierDisplayMap[modifier] || modifier}
+                                      </span>
+                                      {idx < step.modifiers.length - 1 && (
+                                        <span className="text-slate-400 dark:text-slate-600"> + </span>
+                                      )}
+                                    </Fragment>
+                                  ))}
+                                  
+                                  {Array.isArray(step.modifiers) && step.modifiers.length > 0 && Array.isArray(step.keys) && step.keys.length > 0 && (
+                                    <span className="text-slate-400 dark:text-slate-600"> + </span>
+                                  )}
+                                  
+                                  {Array.isArray(step.keys) && step.keys.map((key, idx) => (
+                                    <Fragment key={`key-${idx}`}>
+                                      <span className="font-medium text-blue-600 dark:text-blue-200">
+                                        {keyDisplayMap[key] || key}
+                                      </span>
+                                      {idx < step.keys.length - 1 && (
+                                        <span className="text-slate-400 dark:text-slate-600"> + </span>
+                                      )}
+                                    </Fragment>
+                                  ))}
+                                </>
+                              ) : (
+                                <span className="font-medium text-slate-500 dark:text-slate-400">Delay only</span>
+                              )}
+                              <span className="ml-1 text-slate-400 dark:text-slate-500">({step.delay}ms)</span>
                             </span>
-                          );
-                        })}
-                      </span>
-                    </p>
-                  </div>
-                  
-                  <div className="flex items-center gap-1 ml-4">
-                    {macroToDelete === macro.id ? (
-                      <div className="flex items-center gap-2">
-                        <span className="text-sm text-slate-600 dark:text-slate-400">
-                          Delete macro?
-                        </span>
-                        <div className="flex items-center gap-x-2">
-                          <Button
-                            size="XS"
-                            theme="danger"
-                            text="Yes"
-                            onClick={() => {
-                              handleDeleteMacro(macro.id);
-                            }}
-                            disabled={isDeleting}
-                          />
-                          <Button
-                            size="XS"
-                            theme="light"
-                            text="No"
-                            onClick={() => setMacroToDelete(null)}
-                          />
-                        </div>
-                      </div>
-                    ) : (
-                      <>
-                        <Button
-                          size="XS"
-                          theme="light"
-                          LeadingIcon={LuPenLine}
-                          onClick={() => handleEditMacro(macro)}
-                        />
-                        <Button
-                          size="XS"
-                          theme="light"
-                          LeadingIcon={LuCopy}
-                          onClick={() => handleDuplicateMacro(macro)}
-                        />
-                        <Button
-                          size="XS"
-                          theme="light"
-                          LeadingIcon={LuTrash}
-                          onClick={() => setMacroToDelete(macro.id)}
-                          className="text-red-500 dark:text-red-400"
-                        />
-                      </>
-                    )}
-                  </div>
+                          </span>
+                        );
+                      })}
+                    </span>
+                  </p>
                 </div>
-              )
+                
+                <div className="flex items-center gap-1 ml-4">
+                  <Button
+                    size="XS"
+                    theme="light"
+                    LeadingIcon={LuCopy}
+                    onClick={() => handleDuplicateMacro(macro)}
+                  />
+                  <Button
+                    size="XS"
+                    theme="light"
+                    LeadingIcon={LuPenLine}
+                    onClick={() => navigate(`${macro.id}/edit`)}
+                  />
+                </div>
+              </div>
             )}
           </SortableList>
         )}