import { Theme } from "theme.slint";
import { SftpPanel, SftpEntry, SftpTreeNode } from "sftp_panel.slint";

// Embed the terminal fonts here too so the cell-probe Text below measures
// with the CORRECT font (Cascadia Mono), not a system fallback.  Font imports
// in app.slint are globally available in theory, but some Slint builds only
// register them in the entry-point file's component tree; importing them here
// guarantees the probe uses Cascadia Mono for its preferred-width calculation.
import "fonts/CascadiaMono-Regular.ttf";
import "fonts/CascadiaMono-Bold.ttf";

// One coloured run of text on the terminal grid.  The Rust side groups
// consecutive vt100 cells that share fg + bg + bold into a single span and
// tags it with its grid position (row, col) and width in cells, so the UI can
// place both a background fill and the text with absolute monospace coordinates.
// `bg` is transparent (alpha 0) when the cell uses the default background.
export struct TermSpan {
    text: string,
    fg: color,
    bg: color,
    bold: bool,
    use-fallback-font: bool,
    row: int,
    col: int,
    cells: int,
}

// A find-match highlight rectangle on the terminal grid.
export struct TermMatch {
    row: int,
    col: int,
    len: int,
}

export struct CommandCategory {
    id: string,
    name: string,
    builtin: bool,
}

export struct CommandItem {
    id: string,
    category-id: string,
    name: string,
    command: string,
    append-cr: bool,
}

// --- MenuItem --------------------------------------------------------------
// One row of the terminal right-click context menu (MDL2 icon + label).
component MenuItem inherits Rectangle {
    in property <string> icon;
    in property <string> label;
    callback clicked();

    height: 28px;
    border-radius: Theme.radius-sm;
    background: ta.has-hover ? Theme.bg-hover : transparent;

    ta := TouchArea {
        mouse-cursor: pointer;
        clicked => { root.clicked(); }
    }
    HorizontalLayout {
        height: parent.height;
        padding-left: 8px;
        padding-right: 8px;
        spacing: 8px;
        Text {
            width: 18px;
            height: parent.height;
            text: root.icon;
            font-family: "Material Icons";
            color: Theme.text-secondary;
            font-size: Theme.fs-sm;
            horizontal-alignment: center;
            vertical-alignment: center;
        }
        Text {
            text: root.label;
            color: Theme.text-primary;
            font-size: Theme.fs-md;
            vertical-alignment: center;
            horizontal-stretch: 1;
        }
    }
}

component CommandIconButton inherits Rectangle {
    in property <string> glyph;
    in property <brush> fg: Theme.text-secondary;
    in property <brush> fg-hover: Theme.text-primary;
    callback clicked();

    width: 26px;
    height: 24px;
    border-radius: Theme.radius-sm;
    background: touch.has-hover ? Theme.bg-hover : transparent;

    touch := TouchArea {
        mouse-cursor: pointer;
        clicked => { root.clicked(); }
    }

    Text {
        width: parent.width;
        height: parent.height;
        text: root.glyph;
        font-family: "Material Icons";
        color: touch.has-hover ? root.fg-hover : root.fg;
        font-size: Theme.fs-sm;
        horizontal-alignment: center;
        vertical-alignment: center;
    }
}

component CommandParamField inherits Rectangle {
    in property <string> label;
    in-out property <string> value;
    in property <bool> missing: label != "" && value == "";
    property <string> label-text: label + ":";

    height: 30px;
    vertical-stretch: 0;
    border-radius: Theme.radius-sm;
    border-width: 1px;
    border-color: missing ? Theme.danger : (input.has-focus ? Theme.accent : Theme.border-subtle);
    background: Theme.bg-root;

    label-probe := Text {
        text: root.label-text;
        font-size: Theme.fs-xs;
        opacity: 0;
        x: -1000px;
        y: -1000px;
    }

    label-hover := TouchArea {
        width: parent.width;
        height: parent.height;
    }

    input := TextInput {
        x: 8px;
        y: (parent.height - self.height) / 2;
        width: parent.width - 16px;
        height: 24px;
        text <=> root.value;
        color: Theme.text-primary;
        font-size: Theme.fs-sm;
        single-line: true;
        vertical-alignment: center;
    }

    if root.value == "" : Text {
        x: 10px;
        y: (parent.height - self.height) / 2;
        width: parent.width - 20px;
        height: 22px;
        text: root.label-text;
        color: Theme.text-muted;
        font-size: Theme.fs-xs;
        vertical-alignment: center;
        overflow: elide;
    }

    if label-hover.has-hover && label-probe.preferred-width > root.width - 20px : Rectangle {
        x: 0px;
        y: -28px;
        width: label-probe.preferred-width + 16px;
        height: 24px;
        z: 100;
        border-radius: Theme.radius-sm;
        border-width: 1px;
        border-color: Theme.border-strong;
        background: Theme.bg-panel;
        drop-shadow-blur: 8px;
        drop-shadow-color: #00000050;

        Text {
            x: 8px;
            width: parent.width - 16px;
            height: parent.height;
            text: root.label-text;
            color: Theme.text-primary;
            font-size: Theme.fs-xs;
            vertical-alignment: center;
            overflow: elide;
        }
    }
}

component CommandParamInsertButton inherits Rectangle {
    in property <string> label;
    callback clicked();

    width: 44px;
    height: 24px;
    border-radius: Theme.radius-sm;
    background: touch.has-hover ? Theme.bg-hover : Theme.bg-elevated;

    touch := TouchArea {
        mouse-cursor: pointer;
        clicked => { root.clicked(); }
    }

    Text {
        width: parent.width;
        height: parent.height;
        text: root.label;
        color: Theme.text-secondary;
        font-size: Theme.fs-xs;
        horizontal-alignment: center;
        vertical-alignment: center;
    }
}

// --- CommandManager --------------------------------------------------------
// Bottom dock for FinalShell-style custom commands.
component CommandManager inherits Rectangle {
    in property <[CommandCategory]> command-categories: [];
    in property <[CommandItem]> command-items: [];
    in-out property <string> selected-category;
    in-out property <string> selected-command;
    in-out property <string> category-draft;
    in-out property <string> edit-id;
    in-out property <string> edit-name;
    in-out property <string> edit-command;
    in-out property <bool> edit-append-cr: true;
    in-out property <string> param1-name;
    in-out property <string> param1-value;
    in-out property <string> param2-name;
    in-out property <string> param2-value;
    in-out property <string> param3-name;
    in-out property <string> param3-value;
    in-out property <string> param4-name;
    in-out property <string> param4-value;
    in-out property <string> param5-name;
    in-out property <string> param5-value;

    callback category-select(string);
    callback category-add(string);
    callback category-rename(string, string);
    callback category-delete(string);
    callback command-new(string);
    callback command-select(string);
    callback command-save(string, string, string, string, bool);
    callback command-delete(string);
    callback command-run(string, string, string, string, string, string);
    callback command-move(string, string);

    in-out property <bool> editor-open;
    in-out property <bool> category-editor-open;
    in-out property <string> category-edit-id;
    in-out property <bool> category-can-delete;
    property <bool> params-missing:
        (param1-name != "" && param1-value == "")
        || (param2-name != "" && param2-value == "")
        || (param3-name != "" && param3-value == "")
        || (param4-name != "" && param4-value == "")
        || (param5-name != "" && param5-value == "");
    property <length> ctx-x;
    property <length> ctx-y;

    background: Theme.bg-panel;

    selected-name-probe := Text {
        text: root.edit-name;
        font-size: Theme.fs-sm;
        opacity: 0;
        x: -1000px;
        y: -1000px;
    }

    HorizontalLayout {
        width: parent.width;
        height: parent.height;
        spacing: 0;

        // Categories --------------------------------------------------------
        Rectangle {
            width: 178px;
            background: Theme.bg-panel;

            VerticalLayout {
                spacing: 0;

                Rectangle {
                    height: 28px;
                    background: Theme.bg-root;
                    header-ta := TouchArea {
                        mouse-cursor: default;
                        pointer-event(e) => {
                            if (e.kind == PointerEventKind.down && e.button == PointerEventButton.right) {
                                root.ctx-x = self.mouse-x;
                                root.ctx-y = self.mouse-y;
                                root.category-edit-id = "";
                                root.category-draft = "";
                                root.category-can-delete = false;
                                cat-new-menu.show();
                            }
                        }
                    }
                    HorizontalLayout {
                        padding-left: 8px;
                        padding-right: 4px;
                        spacing: 4px;
                        Text {
                            text: @tr("Categories");
                            color: Theme.text-muted;
                            font-size: Theme.fs-xs;
                            vertical-alignment: center;
                            horizontal-stretch: 1;
                        }
                    }

                    cat-new-menu := PopupWindow {
                        x: root.ctx-x;
                        y: root.ctx-y;
                        width: 132px;
                        height: 36px;

                        Rectangle {
                            background: Theme.bg-panel;
                            border-radius: Theme.radius-sm;
                            border-width: 1px;
                            border-color: Theme.border-strong;
                            drop-shadow-blur: 10px;
                            drop-shadow-color: #00000050;

                            VerticalLayout {
                                padding: 4px;
                                MenuItem {
                                    icon: "\u{E145}";
                                    label: @tr("New category");
                                    clicked => {
                                        cat-new-menu.close();
                                        root.category-editor-open = true;
                                    }
                                }
                            }
                        }
                    }
                }

                Flickable {
                    vertical-stretch: 1;
                    viewport-width: self.width;
                    viewport-height: max(self.height, categories-col.preferred-height);
                    categories-col := VerticalLayout {
                        spacing: 0;
                        for cat[i] in root.command-categories : Rectangle {
                            height: 30px;
                            background: root.selected-category == cat.id
                                ? Theme.bg-active
                                : cat-ta.has-hover
                                ? Theme.bg-hover
                                : (mod(i, 2) == 0 ? transparent : #ffffff08);
                            cat-ta := TouchArea {
                                mouse-cursor: pointer;
                                clicked => { root.category-select(cat.id); }
                                pointer-event(e) => {
                                    if (e.kind == PointerEventKind.down && e.button == PointerEventButton.right) {
                                        root.ctx-x = self.mouse-x;
                                        root.ctx-y = self.mouse-y;
                                        root.category-edit-id = cat.id;
                                        root.category-draft = cat.name;
                                        root.category-can-delete = !cat.builtin;
                                        cat-menu.show();
                                    }
                                }
                            }
                            cat-menu := PopupWindow {
                                x: root.ctx-x;
                                y: root.ctx-y;
                                width: 132px;
                                height: cat.builtin ? 64px : 92px;

                                Rectangle {
                                    background: Theme.bg-panel;
                                    border-radius: Theme.radius-sm;
                                    border-width: 1px;
                                    border-color: Theme.border-strong;
                                    drop-shadow-blur: 10px;
                                    drop-shadow-color: #00000050;

                                    VerticalLayout {
                                        padding: 4px;
                                        spacing: 2px;
                                        MenuItem {
                                            icon: "\u{E145}";
                                            label: @tr("New category");
                                            clicked => {
                                                cat-menu.close();
                                                root.category-edit-id = "";
                                                root.category-draft = "";
                                                root.category-can-delete = false;
                                                root.category-editor-open = true;
                                            }
                                        }
                                        MenuItem {
                                            icon: "\u{E3C9}";
                                            label: @tr("Rename");
                                            clicked => {
                                                cat-menu.close();
                                                root.category-editor-open = true;
                                            }
                                        }
                                        if !cat.builtin : MenuItem {
                                            icon: "\u{E872}";
                                            label: @tr("Delete");
                                            clicked => {
                                                cat-menu.close();
                                                root.category-delete(cat.id);
                                            }
                                        }
                                    }
                                }
                            }
                            HorizontalLayout {
                                height: parent.height;
                                padding-left: 8px;
                                padding-right: 4px;
                                spacing: 6px;
                                Text {
                                    width: 16px;
                                    height: parent.height;
                                    text: "\u{E2C7}";
                                    font-family: "Material Icons";
                                    color: Theme.text-secondary;
                                    font-size: Theme.fs-xs;
                                    horizontal-alignment: center;
                                    vertical-alignment: center;
                                }
                                Text {
                                    text: cat.name;
                                    color: root.selected-category == cat.id ? Theme.text-primary : Theme.text-secondary;
                                    font-size: Theme.fs-sm;
                                    vertical-alignment: center;
                                    horizontal-stretch: 1;
                                    overflow: elide;
                                }
                            }
                        }
                    }
                }
            }
        }

        Rectangle { width: 1px; background: Theme.border-subtle; }

        // Commands + parameter runner --------------------------------------
        VerticalLayout {
            horizontal-stretch: 1;
            spacing: 0;

            Rectangle {
                height: 38px;
                background: Theme.bg-root;
                HorizontalLayout {
                    height: parent.height;
                    padding-left: 8px;
                    padding-right: 8px;
                    spacing: 8px;
                    alignment: center;

                    if root.selected-command != "" : Text {
                        height: parent.height;
                        text: root.edit-name;
                        color: Theme.text-primary;
                        font-size: Theme.fs-sm;
                        font-weight: 600;
                        vertical-alignment: center;
                        width: min(160px, max(52px, selected-name-probe.preferred-width));
                        overflow: elide;
                    }

                    if root.param1-name != "" : CommandParamField {
                        y: (parent.height - self.height) / 2;
                        width: 132px;
                        label: root.param1-name;
                        value <=> root.param1-value;
                    }
                    if root.param2-name != "" : CommandParamField {
                        y: (parent.height - self.height) / 2;
                        width: 132px;
                        label: root.param2-name;
                        value <=> root.param2-value;
                    }
                    if root.param3-name != "" : CommandParamField {
                        y: (parent.height - self.height) / 2;
                        width: 132px;
                        label: root.param3-name;
                        value <=> root.param3-value;
                    }
                    if root.param4-name != "" : CommandParamField {
                        y: (parent.height - self.height) / 2;
                        width: 132px;
                        label: root.param4-name;
                        value <=> root.param4-value;
                    }
                    if root.param5-name != "" : CommandParamField {
                        y: (parent.height - self.height) / 2;
                        width: 132px;
                        label: root.param5-name;
                        value <=> root.param5-value;
                    }

                    if root.params-missing : Text {
                        height: parent.height;
                        text: @tr("Missing parameters");
                        color: Theme.danger;
                        font-size: Theme.fs-xs;
                        font-weight: 600;
                        vertical-alignment: center;
                    }

                    Rectangle { horizontal-stretch: 1; }
                }
            }

            Rectangle { height: 1px; background: Theme.border-subtle; }

            Rectangle {
                vertical-stretch: 1;
                background: Theme.bg-panel;

                cmd-bg-ta := TouchArea {
                    pointer-event(e) => {
                        if (e.kind == PointerEventKind.down && e.button == PointerEventButton.right) {
                            root.ctx-x = self.mouse-x;
                            root.ctx-y = self.mouse-y;
                            cmd-new-menu.show();
                        }
                    }
                }

                Flickable {
                    width: parent.width;
                    height: parent.height;
                    horizontal-stretch: 1;
                    vertical-stretch: 1;
                    viewport-width: max(self.width, commands-row.preferred-width);
                    viewport-height: self.height;

                    commands-row := HorizontalLayout {
                        padding-left: 8px;
                        padding-right: 8px;
                        padding-top: 8px;
                        spacing: 6px;

                        for cmd[i] in root.command-items : Rectangle {
                            visible: cmd.category-id == root.selected-category;
                            width: cmd.category-id == root.selected-category
                                ? min(168px, max(48px, cmd-name-probe.preferred-width + 24px))
                                : 0px;
                            height: cmd.category-id == root.selected-category ? 28px : 0px;
                            border-radius: Theme.radius-sm;
                            background: root.selected-command == cmd.id
                                ? Theme.bg-active
                                : cmd-row-ta.has-hover
                                ? Theme.bg-hover
                                : Theme.bg-elevated;
                            cmd-row-ta := TouchArea {
                                mouse-cursor: pointer;
                                clicked => {
                                    if (root.selected-command != cmd.id) {
                                        root.command-select(cmd.id);
                                    }
                                }
                                pointer-event(e) => {
                                    if (e.kind == PointerEventKind.down && e.button == PointerEventButton.right) {
                                        root.ctx-x = self.mouse-x;
                                        root.ctx-y = self.mouse-y;
                                        if (root.selected-command != cmd.id) {
                                            root.command-select(cmd.id);
                                        }
                                        cmd-menu.show();
                                    }
                                }
                                double-clicked => {
                                    root.command-run(cmd.id, root.param1-value, root.param2-value, root.param3-value, root.param4-value, root.param5-value);
                                }
                            }
                            cmd-menu := PopupWindow {
                                x: root.ctx-x;
                                y: root.ctx-y;
                                width: 132px;
                                height: 92px;

                                Rectangle {
                                    background: Theme.bg-panel;
                                    border-radius: Theme.radius-sm;
                                    border-width: 1px;
                                    border-color: Theme.border-strong;
                                    drop-shadow-blur: 10px;
                                    drop-shadow-color: #00000050;

                                    VerticalLayout {
                                        padding: 4px;
                                        spacing: 2px;
                                        MenuItem {
                                            icon: "\u{E037}";
                                            label: @tr("Run");
                                            clicked => {
                                                cmd-menu.close();
                                                root.command-run(cmd.id, root.param1-value, root.param2-value, root.param3-value, root.param4-value, root.param5-value);
                                            }
                                        }
                                        MenuItem {
                                            icon: "\u{E3C9}";
                                            label: @tr("Edit");
                                            clicked => {
                                                cmd-menu.close();
                                                root.editor-open = true;
                                            }
                                        }
                                        MenuItem {
                                            icon: "\u{E872}";
                                            label: @tr("Delete");
                                            clicked => {
                                                cmd-menu.close();
                                                root.command-delete(cmd.id);
                                            }
                                        }
                                    }
                                }
                            }

                            cmd-name-probe := Text {
                                text: cmd.name;
                                font-size: Theme.fs-sm;
                                opacity: 0;
                                x: -1000px;
                                y: -1000px;
                            }

                            Text {
                                x: 10px;
                                width: parent.width - 20px;
                                height: parent.height;
                                text: cmd.name;
                                color: Theme.text-primary;
                                font-size: Theme.fs-sm;
                                vertical-alignment: center;
                                horizontal-alignment: center;
                                overflow: elide;
                            }

                            if cmd.append-cr : Rectangle {
                                x: parent.width - 7px;
                                y: 5px;
                                width: 4px;
                                height: 4px;
                                border-radius: 2px;
                                background: Theme.success;
                            }
                        }
                    }
                }

                cmd-new-menu := PopupWindow {
                    x: root.ctx-x;
                    y: root.ctx-y;
                    width: 132px;
                    height: 36px;

                    Rectangle {
                        background: Theme.bg-panel;
                        border-radius: Theme.radius-sm;
                        border-width: 1px;
                        border-color: Theme.border-strong;
                        drop-shadow-blur: 10px;
                        drop-shadow-color: #00000050;

                        VerticalLayout {
                            padding: 4px;
                            MenuItem {
                                icon: "\u{E145}";
                                label: @tr("New command");
                                clicked => {
                                    cmd-new-menu.close();
                                    root.command-new(root.selected-category);
                                    root.editor-open = true;
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    if false : Rectangle {
        width: parent.width;
        height: parent.height;
        background: #00000088;

        close-backdrop := TouchArea {
            clicked => { root.editor-open = false; }
        }

        Rectangle {
            x: 24px;
            y: 8px;
            width: parent.width - 48px;
            height: parent.height - 16px;
            border-radius: Theme.radius-md;
            border-width: 1px;
            border-color: Theme.border-strong;
            background: Theme.bg-panel;
            drop-shadow-blur: 16px;
            drop-shadow-color: #00000070;

            swallow := TouchArea { clicked => {} }

            VerticalLayout {
                padding: 10px;
                spacing: 8px;

                HorizontalLayout {
                    height: 26px;
                    spacing: 8px;
                    Text {
                        text: root.edit-id == "" ? @tr("New command") : @tr("Command detail");
                        color: Theme.text-primary;
                        font-size: Theme.fs-sm;
                        font-weight: 600;
                        vertical-alignment: center;
                        horizontal-stretch: 1;
                    }
                    CommandIconButton {
                        y: (parent.height - self.height) / 2;
                        glyph: "\u{E5CD}";
                        clicked => { root.editor-open = false; }
                    }
                }

                HorizontalLayout {
                    height: 28px;
                    spacing: 8px;
                    Rectangle {
                        horizontal-stretch: 1;
                        border-radius: Theme.radius-sm;
                        border-width: 1px;
                        border-color: name-input.has-focus ? Theme.accent : Theme.border-subtle;
                        background: Theme.bg-root;
                        name-input := TextInput {
                            x: 8px;
                            width: parent.width - 16px;
                            height: parent.height;
                            text <=> root.edit-name;
                            color: Theme.text-primary;
                            font-size: Theme.fs-sm;
                            single-line: true;
                            vertical-alignment: center;
                        }
                    }
                    Rectangle {
                        width: 118px;
                        height: parent.height;
                        border-radius: Theme.radius-sm;
                        background: root.edit-append-cr ? Theme.bg-active : Theme.bg-elevated;
                        cr-ta := TouchArea {
                            mouse-cursor: pointer;
                            clicked => { root.edit-append-cr = !root.edit-append-cr; }
                        }
                        HorizontalLayout {
                            height: parent.height;
                            padding-left: 8px;
                            padding-right: 8px;
                            spacing: 6px;
                            Text {
                                width: 16px;
                                height: parent.height;
                                text: root.edit-append-cr ? "\u{E5CA}" : "";
                                font-family: "Material Icons";
                                color: Theme.accent;
                                font-size: Theme.fs-xs;
                                horizontal-alignment: center;
                                vertical-alignment: center;
                            }
                            Text {
                                height: parent.height;
                                text: @tr("Append CR");
                                color: Theme.text-secondary;
                                font-size: Theme.fs-xs;
                                vertical-alignment: center;
                            }
                        }
                    }
                }

                Rectangle {
                    height: 52px;
                    border-radius: Theme.radius-sm;
                    border-width: 1px;
                    border-color: command-input.has-focus ? Theme.accent : Theme.border-subtle;
                    background: Theme.bg-root;
                    command-input := TextInput {
                        x: 8px;
                        width: parent.width - 16px;
                        height: parent.height;
                        text <=> root.edit-command;
                        color: Theme.text-primary;
                        font-size: Theme.fs-sm;
                        vertical-alignment: center;
                    }
                }

                HorizontalLayout {
                    height: 24px;
                    spacing: 6px;
                    CommandParamInsertButton {
                        label: "p#1";
                        clicked => { root.edit-command = root.edit-command + (root.edit-command == "" ? "[p#1 参数1]" : " [p#1 参数1]"); }
                    }
                    CommandParamInsertButton {
                        label: "p#2";
                        clicked => { root.edit-command = root.edit-command + (root.edit-command == "" ? "[p#2 参数2]" : " [p#2 参数2]"); }
                    }
                    CommandParamInsertButton {
                        label: "p#3";
                        clicked => { root.edit-command = root.edit-command + (root.edit-command == "" ? "[p#3 参数3]" : " [p#3 参数3]"); }
                    }
                    CommandParamInsertButton {
                        label: "p#4";
                        clicked => { root.edit-command = root.edit-command + (root.edit-command == "" ? "[p#4 参数4]" : " [p#4 参数4]"); }
                    }
                    CommandParamInsertButton {
                        label: "p#5";
                        clicked => { root.edit-command = root.edit-command + (root.edit-command == "" ? "[p#5 参数5]" : " [p#5 参数5]"); }
                    }
                    Rectangle { horizontal-stretch: 1; }
                }

                if root.command-categories.length > 1 && root.edit-id != "" : HorizontalLayout {
                    height: 24px;
                    spacing: 4px;
                    for cat in root.command-categories : Rectangle {
                        y: (parent.height - self.height) / 2;
                        width: 72px;
                        height: 22px;
                        border-radius: Theme.radius-sm;
                        background: cat.id == root.selected-category
                            ? Theme.bg-active
                            : (move-cat-ta.has-hover ? Theme.bg-hover : Theme.bg-elevated);
                        move-cat-ta := TouchArea {
                            mouse-cursor: pointer;
                            clicked => { root.command-move(root.edit-id, cat.id); }
                        }
                        Text {
                            width: parent.width;
                            height: parent.height;
                            text: cat.name;
                            color: Theme.text-secondary;
                            font-size: Theme.fs-xs;
                            horizontal-alignment: center;
                            vertical-alignment: center;
                            overflow: elide;
                        }
                    }
                }

                Rectangle { vertical-stretch: 1; }

                HorizontalLayout {
                    height: 28px;
                    spacing: 8px;
                    if root.edit-id != "" : Rectangle {
                        width: 72px;
                        height: parent.height;
                        border-radius: Theme.radius-sm;
                        background: del-cmd-ta.has-hover ? #cc333320 : Theme.bg-elevated;
                        del-cmd-ta := TouchArea {
                            mouse-cursor: pointer;
                            clicked => {
                                root.command-delete(root.edit-id);
                                root.editor-open = false;
                            }
                        }
                        HorizontalLayout {
                            height: parent.height;
                            padding-left: 10px;
                            padding-right: 10px;
                            spacing: 6px;
                            Text {
                                width: 16px;
                                height: parent.height;
                                text: "\u{E872}";
                                font-family: "Material Icons";
                                color: Theme.danger;
                                font-size: Theme.fs-xs;
                                horizontal-alignment: center;
                                vertical-alignment: center;
                            }
                            Text {
                                height: parent.height;
                                text: @tr("Delete");
                                color: Theme.danger;
                                font-size: Theme.fs-xs;
                                vertical-alignment: center;
                            }
                        }
                    }
                    Rectangle { horizontal-stretch: 1; }
                    Rectangle {
                        width: 72px;
                        height: parent.height;
                        border-radius: Theme.radius-sm;
                        background: cancel-ta.has-hover ? Theme.bg-hover : Theme.bg-elevated;
                        cancel-ta := TouchArea {
                            mouse-cursor: pointer;
                            clicked => { root.editor-open = false; }
                        }
                        Text {
                            width: parent.width;
                            height: parent.height;
                            text: @tr("Cancel");
                            color: Theme.text-secondary;
                            font-size: Theme.fs-xs;
                            horizontal-alignment: center;
                            vertical-alignment: center;
                        }
                    }
                    Rectangle {
                        width: 72px;
                        height: parent.height;
                        border-radius: Theme.radius-sm;
                        background: save-cmd-ta.has-hover ? Theme.accent-hover : Theme.accent;
                        save-cmd-ta := TouchArea {
                            mouse-cursor: pointer;
                            clicked => {
                                root.command-save(root.edit-id, root.selected-category, root.edit-name, root.edit-command, root.edit-append-cr);
                                root.editor-open = false;
                            }
                        }
                        Text {
                            width: parent.width;
                            height: parent.height;
                            text: @tr("Save");
                            color: #ffffff;
                            font-size: Theme.fs-xs;
                            horizontal-alignment: center;
                            vertical-alignment: center;
                        }
                    }
                }
            }
        }
    }

}

component CommandEditorModal inherits Rectangle {
    in-out property <bool> is-open;
    in property <[CommandCategory]> command-categories: [];
    in-out property <string> selected-category;
    in-out property <string> edit-id;
    in-out property <string> edit-name;
    in-out property <string> edit-command;
    in-out property <bool> edit-append-cr: true;
    callback command-save(string, string, string, string, bool);
    callback command-delete(string);
    callback command-move(string, string);

    visible: is-open;
    background: #00000088;

    TouchArea {
        clicked => { root.is-open = false; }
    }

    Rectangle {
        x: (parent.width - self.width) / 2;
        y: (parent.height - self.height) / 2;
        width: min(540px, parent.width - 48px);
        height: 260px;
        border-radius: Theme.radius-md;
        border-width: 1px;
        border-color: Theme.border-strong;
        background: Theme.bg-panel;
        drop-shadow-blur: 18px;
        drop-shadow-color: #00000080;

        TouchArea {}

        VerticalLayout {
            padding: 12px;
            spacing: 8px;

            HorizontalLayout {
                height: 26px;
                spacing: 8px;
                Text {
                    text: root.edit-id == "" ? @tr("New command") : @tr("Command detail");
                    color: Theme.text-primary;
                    font-size: Theme.fs-sm;
                    font-weight: 600;
                    vertical-alignment: center;
                    horizontal-stretch: 1;
                }
                CommandIconButton {
                    y: (parent.height - self.height) / 2;
                    glyph: "\u{E5CD}";
                    clicked => { root.is-open = false; }
                }
            }

            HorizontalLayout {
                height: 28px;
                spacing: 8px;
                Rectangle {
                    horizontal-stretch: 1;
                    border-radius: Theme.radius-sm;
                    border-width: 1px;
                    border-color: name-input.has-focus ? Theme.accent : Theme.border-subtle;
                    background: Theme.bg-root;
                    name-input := TextInput {
                        x: 8px;
                        width: parent.width - 16px;
                        height: parent.height;
                        text <=> root.edit-name;
                        color: Theme.text-primary;
                        font-size: Theme.fs-sm;
                        single-line: true;
                        vertical-alignment: center;
                    }
                }
                Rectangle {
                    width: 118px;
                    height: parent.height;
                    border-radius: Theme.radius-sm;
                    background: root.edit-append-cr ? Theme.bg-active : Theme.bg-elevated;
                    cr-ta := TouchArea {
                        mouse-cursor: pointer;
                        clicked => { root.edit-append-cr = !root.edit-append-cr; }
                    }
                    HorizontalLayout {
                        height: parent.height;
                        padding-left: 8px;
                        padding-right: 8px;
                        spacing: 6px;
                        Text {
                            width: 16px;
                            height: parent.height;
                            text: root.edit-append-cr ? "\u{E5CA}" : "";
                            font-family: "Material Icons";
                            color: Theme.accent;
                            font-size: Theme.fs-xs;
                            horizontal-alignment: center;
                            vertical-alignment: center;
                        }
                        Text {
                            height: parent.height;
                            text: @tr("Append CR");
                            color: Theme.text-secondary;
                            font-size: Theme.fs-xs;
                            vertical-alignment: center;
                        }
                    }
                }
            }

            Rectangle {
                height: 52px;
                border-radius: Theme.radius-sm;
                border-width: 1px;
                border-color: command-input.has-focus ? Theme.accent : Theme.border-subtle;
                background: Theme.bg-root;
                command-input := TextInput {
                    x: 8px;
                    width: parent.width - 16px;
                    height: parent.height;
                    text <=> root.edit-command;
                    color: Theme.text-primary;
                    font-size: Theme.fs-sm;
                    vertical-alignment: center;
                }
            }

            HorizontalLayout {
                height: 24px;
                spacing: 6px;
                CommandParamInsertButton {
                    label: "p#1";
                    clicked => { root.edit-command = root.edit-command + (root.edit-command == "" ? "[p#1 参数1]" : " [p#1 参数1]"); }
                }
                CommandParamInsertButton {
                    label: "p#2";
                    clicked => { root.edit-command = root.edit-command + (root.edit-command == "" ? "[p#2 参数2]" : " [p#2 参数2]"); }
                }
                CommandParamInsertButton {
                    label: "p#3";
                    clicked => { root.edit-command = root.edit-command + (root.edit-command == "" ? "[p#3 参数3]" : " [p#3 参数3]"); }
                }
                CommandParamInsertButton {
                    label: "p#4";
                    clicked => { root.edit-command = root.edit-command + (root.edit-command == "" ? "[p#4 参数4]" : " [p#4 参数4]"); }
                }
                CommandParamInsertButton {
                    label: "p#5";
                    clicked => { root.edit-command = root.edit-command + (root.edit-command == "" ? "[p#5 参数5]" : " [p#5 参数5]"); }
                }
                Rectangle { horizontal-stretch: 1; }
            }

            if root.command-categories.length > 1 && root.edit-id != "" : HorizontalLayout {
                height: 24px;
                spacing: 4px;
                for cat in root.command-categories : Rectangle {
                    y: (parent.height - self.height) / 2;
                    width: 72px;
                    height: 22px;
                    border-radius: Theme.radius-sm;
                    background: cat.id == root.selected-category
                        ? Theme.bg-active
                        : (move-cat-ta.has-hover ? Theme.bg-hover : Theme.bg-elevated);
                    move-cat-ta := TouchArea {
                        mouse-cursor: pointer;
                        clicked => { root.command-move(root.edit-id, cat.id); }
                    }
                    Text {
                        width: parent.width;
                        height: parent.height;
                        text: cat.name;
                        color: Theme.text-secondary;
                        font-size: Theme.fs-xs;
                        horizontal-alignment: center;
                        vertical-alignment: center;
                        overflow: elide;
                    }
                }
            }

            Rectangle { vertical-stretch: 1; }

            HorizontalLayout {
                height: 28px;
                spacing: 8px;
                if root.edit-id != "" : Rectangle {
                    width: 72px;
                    height: parent.height;
                    border-radius: Theme.radius-sm;
                    background: del-cmd-ta.has-hover ? #cc333320 : Theme.bg-elevated;
                    del-cmd-ta := TouchArea {
                        mouse-cursor: pointer;
                        clicked => {
                            root.command-delete(root.edit-id);
                            root.is-open = false;
                        }
                    }
                    Text {
                        width: parent.width;
                        height: parent.height;
                        text: @tr("Delete");
                        color: Theme.danger;
                        font-size: Theme.fs-xs;
                        horizontal-alignment: center;
                        vertical-alignment: center;
                    }
                }
                Rectangle { horizontal-stretch: 1; }
                Rectangle {
                    width: 72px;
                    height: parent.height;
                    border-radius: Theme.radius-sm;
                    background: cancel-ta.has-hover ? Theme.bg-hover : Theme.bg-elevated;
                    cancel-ta := TouchArea {
                        mouse-cursor: pointer;
                        clicked => { root.is-open = false; }
                    }
                    Text {
                        width: parent.width;
                        height: parent.height;
                        text: @tr("Cancel");
                        color: Theme.text-secondary;
                        font-size: Theme.fs-xs;
                        horizontal-alignment: center;
                        vertical-alignment: center;
                    }
                }
                Rectangle {
                    width: 72px;
                    height: parent.height;
                    border-radius: Theme.radius-sm;
                    background: save-cmd-ta.has-hover ? Theme.accent-hover : Theme.accent;
                    save-cmd-ta := TouchArea {
                        mouse-cursor: pointer;
                        clicked => {
                            root.command-save(root.edit-id, root.selected-category, root.edit-name, root.edit-command, root.edit-append-cr);
                            root.is-open = false;
                        }
                    }
                    Text {
                        width: parent.width;
                        height: parent.height;
                        text: @tr("Save");
                        color: #ffffff;
                        font-size: Theme.fs-xs;
                        horizontal-alignment: center;
                        vertical-alignment: center;
                    }
                }
            }
        }
    }

}

component CategoryEditorModal inherits Rectangle {
    in-out property <bool> is-open;
    in-out property <string> edit-id;
    in-out property <string> draft;
    in property <bool> can-delete: false;
    callback category-add(string);
    callback category-rename(string, string);
    callback category-delete(string);

    visible: is-open;
    background: #00000088;

    TouchArea {
        clicked => { root.is-open = false; }
    }

    Rectangle {
        x: (parent.width - self.width) / 2;
        y: (parent.height - self.height) / 2;
        width: min(360px, parent.width - 48px);
        height: 148px;
        border-radius: Theme.radius-md;
        border-width: 1px;
        border-color: Theme.border-strong;
        background: Theme.bg-panel;
        drop-shadow-blur: 18px;
        drop-shadow-color: #00000080;

        TouchArea {}

        VerticalLayout {
            padding: 12px;
            spacing: 10px;

            HorizontalLayout {
                height: 24px;
                Text {
                    text: root.edit-id == "" ? @tr("New category") : @tr("Rename category");
                    color: Theme.text-primary;
                    font-size: Theme.fs-sm;
                    font-weight: 600;
                    vertical-alignment: center;
                    horizontal-stretch: 1;
                }
                CommandIconButton {
                    y: (parent.height - self.height) / 2;
                    glyph: "\u{E5CD}";
                    clicked => { root.is-open = false; }
                }
            }

            Rectangle {
                height: 28px;
                border-radius: Theme.radius-sm;
                border-width: 1px;
                border-color: category-input.has-focus ? Theme.accent : Theme.border-subtle;
                background: Theme.bg-root;
                category-input := TextInput {
                    x: 8px;
                    width: parent.width - 16px;
                    height: parent.height;
                    text <=> root.draft;
                    color: Theme.text-primary;
                    font-size: Theme.fs-sm;
                    single-line: true;
                    vertical-alignment: center;
                }
            }

            HorizontalLayout {
                height: 28px;
                spacing: 8px;
                if root.can-delete : Rectangle {
                    width: 72px;
                    height: parent.height;
                    border-radius: Theme.radius-sm;
                    background: cat-delete-ta.has-hover ? #cc333320 : Theme.bg-elevated;
                    cat-delete-ta := TouchArea {
                        mouse-cursor: pointer;
                        clicked => {
                            root.category-delete(root.edit-id);
                            root.is-open = false;
                        }
                    }
                    Text {
                        width: parent.width;
                        height: parent.height;
                        text: @tr("Delete");
                        color: Theme.danger;
                        font-size: Theme.fs-xs;
                        horizontal-alignment: center;
                        vertical-alignment: center;
                    }
                }
                Rectangle { horizontal-stretch: 1; }
                Rectangle {
                    width: 72px;
                    height: parent.height;
                    border-radius: Theme.radius-sm;
                    background: cat-cancel-ta.has-hover ? Theme.bg-hover : Theme.bg-elevated;
                    cat-cancel-ta := TouchArea {
                        mouse-cursor: pointer;
                        clicked => { root.is-open = false; }
                    }
                    Text {
                        width: parent.width;
                        height: parent.height;
                        text: @tr("Cancel");
                        color: Theme.text-secondary;
                        font-size: Theme.fs-xs;
                        horizontal-alignment: center;
                        vertical-alignment: center;
                    }
                }
                Rectangle {
                    width: 72px;
                    height: parent.height;
                    border-radius: Theme.radius-sm;
                    background: cat-save-ta.has-hover ? Theme.accent-hover : Theme.accent;
                    cat-save-ta := TouchArea {
                        mouse-cursor: pointer;
                        clicked => {
                            if (root.edit-id == "") {
                                root.category-add(root.draft);
                            } else {
                                root.category-rename(root.edit-id, root.draft);
                            }
                            root.is-open = false;
                        }
                    }
                    Text {
                        width: parent.width;
                        height: parent.height;
                        text: @tr("Save");
                        color: #ffffff;
                        font-size: Theme.fs-xs;
                        horizontal-alignment: center;
                        vertical-alignment: center;
                    }
                }
            }
        }
    }
}

// --- TerminalView ----------------------------------------------------------

export component TerminalView inherits Rectangle {
    in property <string> tab-id;
    in property <string> status-line: @tr("Not connected");
    in property <[TermSpan]> spans;     // coloured runs of the visible screen
    in property <int> cursor-row;       // cursor cell (grid coords)
    in property <int> cursor-col;
    in property <int> rows-used;        // content rows, for viewport sizing
    in property <bool>   is-alt-screen;
    in property <bool>   mouse-tracking: false;
    in property <[TermMatch]> find-matches; // search-highlight rectangles
    in property <[TermMatch]> selection;    // drag-selection highlight rectangles

    in property <string>  sftp-path: "/";
    in property <[SftpEntry]> sftp-entries;
    in property <string>  sftp-status: "";
    in property <bool>    sftp-loading: false;
    in property <[SftpTreeNode]> sftp-tree-nodes: [];
    in property <length> terminal-font-size: 13px;
    in property <bool> click-debug-enabled: false;
    in property <string> background-path;
    in property <int> background-mode: 1;
    in property <image> background-image;
    in property <bool> app-background-active: false;
    in property <float> terminal-bg-opacity: 0.88;
    in property <float> ui-opacity: 0.72;
    in property <length> sftp-file-font-size: 16px;
    in-out property <length> sftp-name-column-width: 180px;
    in-out property <bool> sftp-name-width-manual: false;
    in property <[CommandCategory]> command-categories: [];
    in property <[CommandItem]> command-items: [];
    in-out property <string> command-selected-category;
    in-out property <string> command-selected-id;
    in-out property <string> command-category-draft;
    in-out property <string> command-edit-id;
    in-out property <string> command-edit-name;
    in-out property <string> command-edit-command;
    in-out property <bool> command-edit-append-cr: true;
    in-out property <string> command-param1-name;
    in-out property <string> command-param1-value;
    in-out property <string> command-param2-name;
    in-out property <string> command-param2-value;
    in-out property <string> command-param3-name;
    in-out property <string> command-param3-value;
    in-out property <string> command-param4-name;
    in-out property <string> command-param4-value;
    in-out property <string> command-param5-name;
    in-out property <string> command-param5-value;
    property <bool> command-editor-open: false;
    property <bool> command-category-editor-open: false;
    property <string> command-category-edit-id;
    property <bool> command-category-can-delete: false;

    in-out property <length> sftp-panel-height: 220px;

    callback send-key(string, bool, bool, bool);
    callback terminal-resize(float, float);
    callback sftp-navigate(string);
    callback sftp-download(string);
    callback sftp-upload-clicked(string);
    callback sftp-refresh(string);
    callback sftp-tree-expand(string);
    callback sftp-delete(string);
    callback sftp-rename(string, string);
    callback sftp-edit(string);
    callback sftp-name-column-width-changed(float);
    callback sftp-name-column-auto();
    callback paste-from-clipboard();
    callback copy-terminal-text();
    callback clear-terminal();              // wipe this session's screen buffer
    callback find-query-changed(string);    // recompute search highlights
    callback terminal-scroll(int);          // scroll history by N lines (+ = up)
    callback select-start(int, int);        // begin drag-selection at (row, col)
    callback select-update(int, int);       // extend drag-selection to (row, col)
    callback select-end();                  // finish selection → copy to clipboard
    callback select-autoscroll(int);        // auto-scroll while dragging past edge (dir: -1 up / +1 down)
    callback command-category-select(string);
    callback command-category-add(string);
    callback command-category-rename(string, string);
    callback command-category-delete(string);
    callback command-new(string);
    callback command-select(string);
    callback command-save(string, string, string, string, bool);
    callback command-delete(string);
    callback command-run(string, string, string, string, string, string);
    callback command-move(string, string);
    callback terminal-mouse-event(string, int, int, int);
    callback debug-click(string, string);

    // Right-click context-menu anchor + find-bar visibility (private UI state).
    property <length> ctx-x;
    property <length> ctx-y;
    property <bool> find-active: false;
    property <bool> selecting: false;
    property <int> mouse-drag-button: -1;
    property <length> mouse-drag-start-x: 0px;
    property <length> mouse-drag-start-y: 0px;
    property <bool> mouse-drag-started: false;
    // Auto-scroll direction while drag-selecting past the visible edge.
    // 0 = none, -1 = above top (scroll to older), +1 = below bottom (newer).
    property <int> autoscroll-dir: 0;
    property <length> vis-y: 0px;   // scratch: cursor Y within the visible viewport
    property <int> tool-tab: 0; // 0 files / 1 commands
    property <float> sent-cols: -1;
    property <float> sent-rows: -1;

    background: root.app-background-active
        ? Theme.term-bg.with-alpha(root.terminal-bg-opacity)
        : root.background-mode == 1 && root.background-path != ""
        ? Theme.term-bg.with-alpha(root.terminal-bg-opacity)
        : Theme.term-bg;
    forward-focus: ime-input;

    if root.background-mode == 1 && root.background-path != "" : Image {
        width: parent.width;
        height: parent.height;
        source: root.background-image;
        image-fit: cover;
        opacity: 0.34;
    }

    // Drag state (private)
    property <bool>   sftp-dragging:     false;
    property <length> drag-start-mouse-y: 0px;

    // --- Real cell-size measurement ----------------------------------------
    // A hidden Text rendered with the exact terminal font reports the true
    // Consolas cell width/height for the current font size.  We derive the PTY
    // column/row counts from these measured cells instead of guessing 8×16px,
    // so full-screen programs (nano/vim) get an accurate row count and their
    // bottom shortcut bar is no longer clipped off the visible area.
    // Measure a 50-char run, not a single glyph: a single Text's preferred-width
    // includes left/right side bearings and is wider than the real per-character
    // advance, which would make absolutely-positioned spans drift apart.  The
    // average advance over a long run (side bearings amortised) is accurate.
    // Cell-size probe: must be RENDERED (opacity:0, not visible:false) so Slint
    // resolves the correct embedded font before computing preferred-width/height.
    // visible:false can skip font loading and fall back to a system font (e.g.
    // Consolas ~6.5 px/char instead of Cascadia Mono ~7.6 px/char), giving a
    // cell-w that is too narrow and a term-cols that is too large.
    cell-probe := Text {
        text: "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"; // exactly 50×M
        font-family: "Cascadia Mono";
        font-size: root.terminal-font-size;
        // Keep it in layout but invisible: opacity:0 forces full font loading.
        opacity: 0;
        // Park it at y<0 so it never overlaps terminal content.
        y: -200px;
        width: 0px; // zero out layout contribution
        height: 0px;
    }
    // cell-w / cell-h come straight from the probe so they ALWAYS match the
    // font the spans below are actually rendered with.  (Forcing a hard-coded
    // minimum here is dangerous: if the spans fall back to a system font, a
    // mismatched cell-w garbles the grid.  Keep them derived from one source.)
    property <length> cell-w: max(1px, cell-probe.preferred-width / 50);
    // Guard band avoids clipping glyph ascenders/descenders during repaint.
    property <length> cell-h: max(1px, cell-probe.preferred-height + 2px);
    // Usable text box = root.width minus the 10px left/right padding of the
    // terminal grid. Derive the usable height from TerminalView's outer size and
    // fixed chrome only. Reading key-capture.height or flickable.viewport-height
    // here can feed Slint's preferred-size calculation back into itself when a
    // terminal tab becomes visible, which trips "Recursion detected".
    property <length> key-capture-h: max(
        0px,
        root.height - 24px - 4px - root.sftp-panel-height
    );
    property <length> terminal-viewport-h: root.key-capture-h;
    property <length> terminal-pad-y: 4px;
    property <length> terminal-grid-h: max(0px, root.terminal-viewport-h - root.terminal-pad-y * 2);
    property <length> terminal-content-h: root.is-alt-screen
        ? (root.selecting ? max(root.terminal-grid-h, root.rows-used * root.cell-h) : root.terminal-grid-h)
        : max(root.terminal-grid-h, root.rows-used * root.cell-h);
    property <length> terminal-scroll-h: root.terminal-content-h + root.terminal-pad-y * 2;
    property <float> term-cols: max(10, floor((root.width - 20px) / cell-w));
    property <float> term-rows: max(5,  floor(root.terminal-grid-h / cell-h));
    // Only resize the PTY while this tab is visible. Inactive tabs are laid out
    // at width 0 (-> term-cols collapses to 10); resizing the remote to 10
    // columns reflows the vt100 screen and destroys the scrollback, so hidden
    // sessions stay frozen at their last real size.
    //
    // Do not emit resize from init/changed term-cols/changed term-rows. Those
    // handlers run while Slint is still solving the parent layout and force
    // term-rows -> root.height -> parent layout-cache, which recursively asks
    // this TerminalView for layout info. The timer fires after layout has
    // settled and only forwards actual size changes.
    Timer {
        running: root.visible;
        interval: 200ms;
        triggered => {
            if (root.term-cols != root.sent-cols || root.term-rows != root.sent-rows) {
                root.sent-cols = root.term-cols;
                root.sent-rows = root.term-rows;
                root.terminal-resize(root.term-cols, root.term-rows);
            }
        }
    }

    changed spans => {
        // Bash: keep the latest output pinned to the bottom.
        // Alt-screen (nano/vim/htop/btop): the program owns a fixed full screen,
        // so the viewport must stay top-aligned (viewport-y = 0).  Scrolling to
        // the bottom would push the title bar + content off the top, leaving only
        // the program's status bar — exactly the "nano shows nothing" symptom.
        if (!root.is-alt-screen && !root.selecting) {
            flickable.viewport-y = flickable.height - flickable.viewport-height;
        } else if (!root.selecting) {
            flickable.viewport-y = 0;
        }
    }
    changed is-alt-screen => {
        // Reset scroll on every mode switch (entering/leaving nano, vim, btop…).
        // Snapping to top on alt-screen entry ensures the full-screen program's
        // frame is shown from row 0, not from wherever history left off.
        if (root.is-alt-screen) {
            flickable.viewport-y = 0;
        } else {
            // Return to normal mode: pin to the most recent output.
            flickable.viewport-y = flickable.height - flickable.viewport-height;
        }
    }
    changed visible => {
        if self.visible {
            // Force the timer to re-sync the PTY size on return.
            root.sent-cols = -1;
            root.sent-rows = -1;
            if (!root.command-editor-open && !root.command-category-editor-open) {
                ime-input.focus();
            }
        }
    }

    VerticalLayout {
        spacing: 0;

        // --- Status strip ---------------------------------------------------
        Rectangle {
            height: 24px;
            background: root.app-background-active ? Theme.bg-panel-alt.with-alpha(root.ui-opacity) : Theme.bg-panel-alt;
            HorizontalLayout {
                padding-left: 10px; padding-right: 10px;
                Text {
                    text: root.is-alt-screen ? @tr("Running in alternate screen") : "";
                    color: Theme.text-secondary;
                    font-size: Theme.fs-xs;
                    vertical-alignment: center;
                    horizontal-stretch: 1;
                }
                // Alt-screen indicator only.  IMPORTANT: never put cell-w / cell-h
                // (a Text's preferred-size) inside another Text's `text` — doing so
                // makes Slint compute one text layout *inside* another, which
                // re-enters parley's FontContext RefCell and panics ("already
                // borrowed").  A plain bool flag is safe.
                if root.is-alt-screen : Text {
                    text: "[ALT]";
                    color: Theme.success;
                    font-size: Theme.fs-xs;
                    vertical-alignment: center;
                }
            }
        }

        // --- Key-capture scope + scrollable output --------------------------
        key-capture := FocusScope {
            vertical-stretch: 1;
            // Any click inside this scope (e.g. the middle-click paste strip)
            // can grab focus to the FocusScope itself.  Key forwarding lives on
            // the hidden `ime-input` TextInput, so a bare FocusScope focus means
            // the cursor stops blinking and typing stops working.  Forwarding
            // focus to ime-input guarantees the text input always ends up with
            // the keyboard focus, no matter what grabbed it.
            forward-focus: ime-input;
            property <bool> cursor-blink: true;

            Timer {
                running: ime-input.has-focus;
                interval: 530ms;
                triggered => { key-capture.cursor-blink = !key-capture.cursor-blink; }
            }

            // Auto-scroll while the drag-selection is held past the top/bottom
            // edge: ticks repeatedly so the scrollback keeps moving even when the
            // mouse is held still beyond the edge.
            Timer {
                running: root.selecting && root.autoscroll-dir != 0;
                interval: 50ms;
                triggered => { root.select-autoscroll(root.autoscroll-dir); }
            }

            VerticalLayout {
                spacing: 0;

                flickable := Flickable {
                    vertical-stretch: 1;
                    viewport-width: self.width;
                    // Alt-screen: lock the viewport to exactly the visible height
                    // so the full-screen program (nano/vim) renders top-aligned
                    // with no internal scroll.  Bash: let the viewport grow with
                    // the content so we can scroll back through history.
                    viewport-height: root.terminal-scroll-h;

                    // The terminal grid.  Each coloured run is placed with
                    // absolute coordinates derived from its (row, col) and the
                    // measured Consolas cell size — no per-line layout needed,
                    // which keeps the model flat (a single `for`).
                    Rectangle {
                        x: 10px;
                        y: root.terminal-pad-y;
                        width: parent.width - 20px;
                        height: root.terminal-content-h;

                        // Click-to-focus over the whole terminal body.  Without
                        // this, clicking anywhere in the output area blurs the
                        // hidden ime-input (Slint clears focus on a click that
                        // lands on a non-focusable element), which makes the
                        // cursor vanish and keystrokes stop — recoverable only by
                        // reopening the tab.  This TouchArea sits *below* the text
                        // (Text never grabs pointer events, so clicks fall through
                        // to it) and restores focus on every click. In mouse-
                        // tracking apps such as tmux, unmodified mouse input is
                        // forwarded; Shift+drag/Shift+right-click stay local.
                        body-touch := TouchArea {
                            mouse-cursor: text;
                            pointer-event(ev) => {
                                if (ev.kind == PointerEventKind.down) {
                                    // Any button press re-takes keyboard focus.
                                    ime-input.focus();
                                    if (root.mouse-tracking && !ev.modifiers.shift) {
                                        root.mouse-drag-button =
                                            ev.button == PointerEventButton.left ? 0
                                                : ev.button == PointerEventButton.middle ? 1
                                                : 2;
                                        root.mouse-drag-start-x = self.mouse-x;
                                        root.mouse-drag-start-y = self.mouse-y;
                                        root.mouse-drag-started = false;
                                        root.terminal-mouse-event(
                                            "down",
                                            root.mouse-drag-button,
                                            floor(self.mouse-y / root.cell-h),
                                            floor(self.mouse-x / root.cell-w));
                                    } else if (ev.button == PointerEventButton.right) {
                                        root.ctx-x = self.mouse-x;
                                        root.ctx-y = self.mouse-y;
                                        ctx-menu.show();
                                    } else if (ev.button == PointerEventButton.left
                                            && (!root.mouse-tracking || ev.modifiers.shift)) {
                                        root.selecting = true;
                                        root.select-start(
                                            floor(self.mouse-y / root.cell-h),
                                            floor(self.mouse-x / root.cell-w));
                                    }
                                } else if (ev.kind == PointerEventKind.up) {
                                    if (ev.button == PointerEventButton.middle) {
                                        if (root.mouse-tracking && !ev.modifiers.shift) {
                                            root.terminal-mouse-event(
                                                "up", 1,
                                                floor(self.mouse-y / root.cell-h),
                                                floor(self.mouse-x / root.cell-w));
                                            root.mouse-drag-button = -1;
                                            root.mouse-drag-started = false;
                                        } else {
                                            root.paste-from-clipboard();
                                        }
                                        ime-input.focus();
                                    } else if (ev.button == PointerEventButton.left
                                            && root.selecting) {
                                        root.selecting = false;
                                        root.autoscroll-dir = 0;
                                        root.select-end();
                                    } else if (root.mouse-tracking && !ev.modifiers.shift) {
                                        root.terminal-mouse-event(
                                            "up",
                                            ev.button == PointerEventButton.left ? 0
                                                : ev.button == PointerEventButton.middle ? 1
                                                : 2,
                                            floor(self.mouse-y / root.cell-h),
                                            floor(self.mouse-x / root.cell-w));
                                        root.mouse-drag-button = -1;
                                        root.mouse-drag-started = false;
                                    }
                                }
                            }
                            moved => {
                                if (root.mouse-tracking && !self.pressed && root.mouse-drag-button < 0) {
                                    root.terminal-mouse-event(
                                        "move",
                                        3,
                                        floor(self.mouse-y / root.cell-h),
                                        floor(self.mouse-x / root.cell-w));
                                } else if (root.mouse-tracking && root.mouse-drag-button >= 0) {
                                    if (!root.mouse-drag-started
                                            && abs(self.mouse-x - root.mouse-drag-start-x) < 2px
                                            && abs(self.mouse-y - root.mouse-drag-start-y) < 2px) {
                                        return;
                                    }
                                    root.mouse-drag-started = true;
                                    root.terminal-mouse-event(
                                        "drag",
                                        root.mouse-drag-button,
                                        floor(self.mouse-y / root.cell-h),
                                        floor(self.mouse-x / root.cell-w));
                                } else if (root.selecting) {
                                    // Position of the cursor within the *visible*
                                    // viewport (body-touch sits on the grid Rectangle
                                    // at content y=8px; viewport-y offsets the scroll).
                                    root.select-update(
                                        floor(self.mouse-y / root.cell-h),
                                        floor(self.mouse-x / root.cell-w));
                                    // vy ∈ [0, flickable.height] = where the mouse
                                    // is inside the visible window. Shift+drag in
                                    // tmux still uses local selection, so allow
                                    // edge auto-scroll even on the alt screen.
                                    root.vis-y = self.mouse-y + root.terminal-pad-y + flickable.viewport-y;
                                    if (root.vis-y < root.cell-h) {
                                        root.autoscroll-dir = -1;       // past top → older
                                    } else if (root.vis-y > flickable.height - root.cell-h) {
                                        root.autoscroll-dir = 1;        // past bottom → newer
                                    } else {
                                        root.autoscroll-dir = 0;
                                    }
                                }
                            }
                            scroll-event(ev) => {
                                if (root.mouse-tracking) {
                                    root.terminal-mouse-event(
                                        "wheel",
                                        ev.delta-y > 0 ? 64 : 65,
                                        floor(self.mouse-y / root.cell-h),
                                        floor(self.mouse-x / root.cell-w));
                                } else {
                                    // Wheel scrolls local scrollback (~3 lines per
                                    // notch). Mouse-tracking apps such as tmux own
                                    // their history, so wheel input is forwarded.
                                    root.terminal-scroll(ev.delta-y > 0 ? 3 : -3);
                                    flickable.viewport-y = clamp(
                                        flickable.viewport-y + (ev.delta-y > 0 ? 3 * root.cell-h : -3 * root.cell-h),
                                        0px,
                                        max(0px, flickable.viewport-height - flickable.height)
                                    );
                                }
                                accept
                            }
                        }

                        // Drag-selection highlights (under the text, blue tint).
                        for s in root.selection : Rectangle {
                            x: s.col * root.cell-w;
                            y: s.row * root.cell-h;
                            width: s.len * root.cell-w;
                            height: root.cell-h;
                            background: Theme.accent.with-alpha(0.40);
                        }

                        // Search-match highlights (behind the text).
                        for m in root.find-matches : Rectangle {
                            x: m.col * root.cell-w;
                            y: m.row * root.cell-h;
                            width: m.len * root.cell-w;
                            height: root.cell-h;
                            background: Theme.warning.with-alpha(0.45);
                            border-radius: 2px;
                        }

                        // Each span = a background fill (transparent for the
                        // default bg) with its text on top.  Wrapping the Text in
                        // the bg Rectangle keeps one element per span and renders
                        // btop/htop/mc panels, meter bars and graph fills.
                        for span in root.spans : Rectangle {
                            x: span.col * root.cell-w;
                            y: span.row * root.cell-h;
                            width: span.cells * root.cell-w;
                            height: root.cell-h;
                            background: span.bg;
                            // Keep text constrained to the exact terminal cells
                            // it occupies. Letting Text auto-size can overpaint
                            // the following absolute-positioned cells when the
                            // font fallback/wide-glyph metrics differ slightly.
                            Text {
                                x: 0px;
                                y: 0px;
                                width: parent.width;
                                height: parent.height;
                                text: span.text;
                                color: span.fg;
                                font-family: span.use-fallback-font ? Theme.font-ui : "Cascadia Mono";
                                font-size: root.terminal-font-size;
                                font-weight: span.bold ? 700 : 400;
                                vertical-alignment: center;
                            }
                        }

                        // Blinking block cursor (overlay, decoupled from text).
                        Rectangle {
                            x: root.cursor-col * root.cell-w;
                            y: root.cursor-row * root.cell-h;
                            width: root.cell-w;
                            height: root.cell-h;
                            background: Theme.term-fg;
                            visible: ime-input.has-focus && key-capture.cursor-blink;
                        }

                        // --- Right-click context menu ---------------------
                        ctx-menu := PopupWindow {
                            x: root.ctx-x;
                            y: root.ctx-y;
                            width: 132px;
                            height: 132px;
                            Rectangle {
                                background: Theme.bg-panel;
                                border-radius: Theme.radius-sm;
                                border-width: 1px;
                                border-color: Theme.border-strong;
                                drop-shadow-blur: 12px;
                                drop-shadow-color: #00000060;
                                VerticalLayout {
                                    padding: 4px;
                                    spacing: 1px;
                                    MenuItem {
                                        icon: "\u{E14D}"; label: @tr("Copy");
                                        clicked => {
                                            if root.click-debug-enabled { root.debug-click("terminal.context-menu", "copy"); }
                                            root.copy-terminal-text();
                                            ime-input.focus();
                                        }
                                    }
                                    MenuItem {
                                        icon: "\u{E14F}"; label: @tr("Paste");
                                        clicked => {
                                            if root.click-debug-enabled { root.debug-click("terminal.context-menu", "paste"); }
                                            root.paste-from-clipboard();
                                            ime-input.focus();
                                        }
                                    }
                                    MenuItem {
                                        icon: "\u{E872}"; label: @tr("Clear buffer");
                                        clicked => {
                                            if root.click-debug-enabled { root.debug-click("terminal.context-menu", "clear-buffer"); }
                                            root.clear-terminal();
                                            ime-input.focus();
                                        }
                                    }
                                    MenuItem {
                                        icon: "\u{E8B6}"; label: @tr("Find");
                                        clicked => {
                                            if root.click-debug-enabled { root.debug-click("terminal.context-menu", "find"); }
                                            root.find-active = true;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

            }

            // --- Hidden TextInput — keyboard + Chinese IME capture -----------
            //
            // Key design decision: return `accept` for EVERY key and forward
            // them all via send-key.  Returning `reject` makes Slint bubble the
            // event without inserting the character into `text`, so `edited`
            // never fires for regular keystrokes — only IME-committed chars
            // (which go through a separate WM_IME_CHAR path) reach `edited`.
            //
            // By forwarding everything in key-pressed we guarantee English and
            // all direct-mode input reaches the PTY.  `edited` is kept as a
            // safety net for IME commits that Windows delivers via WM_IME_CHAR
            // rather than WM_CHAR (and therefore don't fire key-pressed).
            ime-input := TextInput {
                x: 0;
                y: 0;
                width: key-capture.width;
                height: 1px;
                opacity: 0;
                single-line: true;

                // Reset blink phase on focus gain so cursor appears immediately.
                changed has-focus => {
                    if (self.has-focus) { key-capture.cursor-blink = true; }
                }

                key-pressed(event) => {
                    // ── Ctrl+Shift+C: copy terminal buffer ───────────────────
                    if (event.modifiers.control && event.modifiers.shift
                            && (event.text == "c" || event.text == "C"
                                || event.text == "\u{0003}")) {
                        root.copy-terminal-text();
                        accept
                    // ── Ctrl+V or Ctrl+Shift+V: paste from clipboard ─────────
                    // We accept Ctrl+V (with or without Shift) so the familiar
                    // Windows paste shortcut works in addition to Ctrl+Shift+V.
                    } else if (event.modifiers.control
                            && (event.text == "v" || event.text == "V"
                                || event.text == "\u{0016}")) {
                        root.paste-from-clipboard();
                        // Clipboard access on Windows can deliver WM_KILLFOCUS.
                        // Restore focus explicitly so the cursor keeps blinking.
                        ime-input.focus();
                        accept
                    // ── All other keys (printable, Ctrl+X, arrows, …) ────────
                    // Return `accept` for everything: `reject` bubbles without
                    // inserting the char into TextInput, so `edited` never fires
                    // for regular keystrokes — the character would be lost.
                    } else {
                        root.send-key(event.text, event.modifiers.control,
                                      event.modifiers.alt, event.modifiers.shift);
                        accept
                    }
                }

                // Safety-net for IME commits delivered via WM_IME_CHAR (which
                // may not fire key-pressed).  Guard with preedit-text == "" to
                // skip in-progress compositions.
                edited => {
                    if (self.preedit-text == "" && self.text != "") {
                        root.send-key(self.text, false, false, false);
                        self.text = "";
                    }
                }
            }

            // --- Find bar (overlay, top) ------------------------------------
            if root.find-active : Rectangle {
                x: 0;
                y: 0;
                width: parent.width;
                height: 34px;
                background: Theme.bg-panel;
                border-width: 1px;
                border-color: Theme.border-strong;
                border-radius: Theme.radius-sm;

                HorizontalLayout {
                    height: parent.height;
                    padding: 6px;
                    spacing: 6px;
                    Text {
                        y: (parent.height - self.height) / 2;
                        width: 18px;
                        height: 22px;
                        text: "\u{E8B6}";
                        font-family: "Material Icons";
                        color: Theme.text-secondary;
                        font-size: Theme.fs-sm;
                        vertical-alignment: center;
                    }
                    Rectangle {
                        y: (parent.height - self.height) / 2;
                        height: 22px;
                        background: Theme.bg-root;
                        border-radius: 3px;
                        horizontal-stretch: 1;
                        find-input := TextInput {
                            x: 6px;
                            width: parent.width - 12px;
                            height: parent.height;
                            color: Theme.text-primary;
                            font-size: Theme.fs-md;
                            vertical-alignment: center;
                            single-line: true;
                            // Grab focus as soon as the bar appears.
                            init => { self.focus(); }
                            edited => { root.find-query-changed(self.text); }
                            key-pressed(e) => {
                                if (e.text == "\u{001b}") {       // Esc closes find
                                    root.find-active = false;
                                    root.find-query-changed("");
                                    self.text = "";
                                    ime-input.focus();
                                    accept
                                } else {
                                    reject
                                }
                            }
                        }
                    }
                    Rectangle {
                        y: (parent.height - self.height) / 2;
                        width: 24px;
                        height: 22px;
                        close-ta := TouchArea {
                            mouse-cursor: pointer;
                            clicked => {
                                root.find-active = false;
                                root.find-query-changed("");
                                find-input.text = "";
                                ime-input.focus();
                            }
                        }
                        Text {
                            width: parent.width;
                            height: parent.height;
                            text: "\u{E5CD}";
                            font-family: "Material Icons";
                            color: close-ta.has-hover ? Theme.text-primary : Theme.text-secondary;
                            font-size: Theme.fs-sm;
                            horizontal-alignment: center;
                            vertical-alignment: center;
                        }
                    }
                }
            }
        }

        // --- Drag handle ----------------------------------------------------
        Rectangle {
            height: 4px;
            background: divider-ta.has-hover || root.sftp-dragging
                ? Theme.accent
                : Theme.border-subtle;

            divider-ta := TouchArea {
                mouse-cursor: row-resize;

                pointer-event(ev) => {
                    if (ev.kind == PointerEventKind.down) {
                        root.sftp-dragging      = true;
                        root.drag-start-mouse-y = self.mouse-y;
                    } else if (ev.kind == PointerEventKind.up) {
                        root.sftp-dragging = false;
                    }
                }

                moved => {
                    if (root.sftp-dragging) {
                        root.sftp-panel-height = clamp(
                            root.sftp-panel-height + root.drag-start-mouse-y - self.mouse-y,
                            60px,
                            root.height - 24px - 4px - 16px - 80px
                        );
                    }
                }
            }
        }

        // --- Bottom tools: files / commands --------------------------------
        Rectangle {
            height: root.sftp-panel-height;
            background: root.app-background-active ? Theme.bg-panel.with-alpha(root.ui-opacity) : Theme.bg-panel;
            clip: true;

            VerticalLayout {
                spacing: 0;

                Rectangle {
                    height: 30px;
                    background: root.app-background-active ? Theme.bg-panel-alt.with-alpha(root.ui-opacity) : Theme.bg-panel-alt;
                    border-width: 1px;
                    border-color: Theme.border-subtle;

                    HorizontalLayout {
                        height: parent.height;
                        padding-left: 8px;
                        padding-right: 8px;
                        spacing: 4px;

                        Rectangle {
                            y: (parent.height - self.height) / 2;
                            width: 96px;
                            height: 24px;
                            border-radius: Theme.radius-sm;
                            background: root.tool-tab == 0
                                ? Theme.bg-active
                                : (files-tab-ta.has-hover ? Theme.bg-hover : transparent);
                            files-tab-ta := TouchArea {
                                mouse-cursor: pointer;
                                clicked => {
                                    if root.click-debug-enabled { root.debug-click("terminal.bottom-tools", "files-tab"); }
                                    root.tool-tab = 0;
                                }
                            }
                            HorizontalLayout {
                                height: parent.height;
                                padding-left: 8px;
                                padding-right: 8px;
                                spacing: 6px;
                                Text {
                                    height: parent.height;
                                    text: "\u{E2C7}";
                                    font-family: "Material Icons";
                                    color: root.tool-tab == 0 ? Theme.text-primary : Theme.text-secondary;
                                    font-size: Theme.fs-sm;
                                    vertical-alignment: center;
                                }
                                Text {
                                    height: parent.height;
                                    text: @tr("File management");
                                    color: root.tool-tab == 0 ? Theme.text-primary : Theme.text-secondary;
                                    font-size: Theme.fs-sm;
                                    vertical-alignment: center;
                                }
                            }
                        }

                        Rectangle {
                            y: (parent.height - self.height) / 2;
                            width: 96px;
                            height: 24px;
                            border-radius: Theme.radius-sm;
                            background: root.tool-tab == 1
                                ? Theme.bg-active
                                : (commands-tab-ta.has-hover ? Theme.bg-hover : transparent);
                            commands-tab-ta := TouchArea {
                                mouse-cursor: pointer;
                                clicked => {
                                    if root.click-debug-enabled { root.debug-click("terminal.bottom-tools", "commands-tab"); }
                                    root.tool-tab = 1;
                                }
                            }
                            HorizontalLayout {
                                height: parent.height;
                                padding-left: 8px;
                                padding-right: 8px;
                                spacing: 6px;
                                Text {
                                    height: parent.height;
                                    text: "\u{E8F4}";
                                    font-family: "Material Icons";
                                    color: root.tool-tab == 1 ? Theme.text-primary : Theme.text-secondary;
                                    font-size: Theme.fs-sm;
                                    vertical-alignment: center;
                                }
                                Text {
                                    height: parent.height;
                                    text: @tr("Command management");
                                    color: root.tool-tab == 1 ? Theme.text-primary : Theme.text-secondary;
                                    font-size: Theme.fs-sm;
                                    vertical-alignment: center;
                                }
                            }
                        }

                        Rectangle { horizontal-stretch: 1; }
                    }
                }

                Rectangle {
                    vertical-stretch: 1;
                    background: root.app-background-active ? Theme.bg-panel.with-alpha(max(0.30, root.ui-opacity - 0.08)) : Theme.bg-panel;
                    clip: true;

                    if root.tool-tab == 0 : SftpPanel {
                        width: parent.width;
                        height: parent.height;
                        current-path:  root.sftp-path;
                        entries:       root.sftp-entries;
                        status:        root.sftp-status;
                        loading:       root.sftp-loading;
                        tree-nodes:    root.sftp-tree-nodes;
                        app-background-active: root.app-background-active;
                        ui-opacity: root.ui-opacity;
                        file-font-size: root.sftp-file-font-size;
                        name-column-width <=> root.sftp-name-column-width;
                        name-column-manual <=> root.sftp-name-width-manual;
                        navigate(path)      => { root.sftp-navigate(path); }
                        download(path)      => { root.sftp-download(path); }
                        upload-clicked(dir) => { root.sftp-upload-clicked(dir); }
                        refresh(path)       => { root.sftp-refresh(path); }
                        tree-expand(path)   => { root.sftp-tree-expand(path); }
                        delete(path)        => { root.sftp-delete(path); }
                        rename(path, name)  => { root.sftp-rename(path, name); }
                        edit(path)          => { root.sftp-edit(path); }
                        name-column-width-changed(px) => { root.sftp-name-column-width-changed(px); }
                        name-column-auto() => { root.sftp-name-column-auto(); }
                    }

                    if root.tool-tab == 1 : CommandManager {
                        width: parent.width;
                        height: parent.height;
                        command-categories: root.command-categories;
                        command-items: root.command-items;
                        selected-category <=> root.command-selected-category;
                        selected-command <=> root.command-selected-id;
                        category-draft <=> root.command-category-draft;
                        edit-id <=> root.command-edit-id;
                        edit-name <=> root.command-edit-name;
                        edit-command <=> root.command-edit-command;
                        edit-append-cr <=> root.command-edit-append-cr;
                        param1-name <=> root.command-param1-name;
                        param1-value <=> root.command-param1-value;
                        param2-name <=> root.command-param2-name;
                        param2-value <=> root.command-param2-value;
                        param3-name <=> root.command-param3-name;
                        param3-value <=> root.command-param3-value;
                        param4-name <=> root.command-param4-name;
                        param4-value <=> root.command-param4-value;
                        param5-name <=> root.command-param5-name;
                        param5-value <=> root.command-param5-value;
                        editor-open <=> root.command-editor-open;
                        category-editor-open <=> root.command-category-editor-open;
                        category-edit-id <=> root.command-category-edit-id;
                        category-can-delete <=> root.command-category-can-delete;
                        category-select(id) => { root.command-category-select(id); }
                        category-add(name) => { root.command-category-add(name); }
                        category-rename(id, name) => { root.command-category-rename(id, name); }
                        category-delete(id) => { root.command-category-delete(id); }
                        command-new(category) => { root.command-new(category); }
                        command-select(id) => { root.command-select(id); }
                        command-save(id, category, name, command, append_cr) => {
                            root.command-save(id, category, name, command, append_cr);
                        }
                        command-delete(id) => { root.command-delete(id); }
                        command-run(id, p1, p2, p3, p4, p5) => {
                            root.command-run(id, p1, p2, p3, p4, p5);
                            ime-input.focus();
                        }
                        command-move(id, category) => { root.command-move(id, category); }
                    }
                }
            }
        }
    }

    CommandEditorModal {
        width: parent.width;
        height: parent.height;
        is-open <=> root.command-editor-open;
        command-categories: root.command-categories;
        selected-category <=> root.command-selected-category;
        edit-id <=> root.command-edit-id;
        edit-name <=> root.command-edit-name;
        edit-command <=> root.command-edit-command;
        edit-append-cr <=> root.command-edit-append-cr;
        command-save(id, category, name, command, append_cr) => {
            root.command-save(id, category, name, command, append_cr);
        }
        command-delete(id) => { root.command-delete(id); }
        command-move(id, category) => { root.command-move(id, category); }
    }

    CategoryEditorModal {
        width: parent.width;
        height: parent.height;
        is-open <=> root.command-category-editor-open;
        edit-id <=> root.command-category-edit-id;
        draft <=> root.command-category-draft;
        can-delete: root.command-category-can-delete;
        category-add(name) => { root.command-category-add(name); }
        category-rename(id, name) => { root.command-category-rename(id, name); }
        category-delete(id) => { root.command-category-delete(id); }
    }
}
